LOGO OA教程 ERP教程 模切知識(shí)交流 PMS教程 CRM教程 開(kāi)發(fā)文檔 其他文檔  
 
網(wǎng)站管理員

C# .NET4通過(guò)HTTP操作MINIO

freeflydom
2025年8月1日 9:41 本文熱度 712

MINIO是提供.NET SDK的,但是這么老的版本沒(méi)找到,于是使用http的方式直接調(diào)用,方便簡(jiǎn)單。

我這里需求不復(fù)雜,只需要上傳下載刪除即可,如果后續(xù)有需求再補(bǔ)充方法。

核心代碼MinioHttpOperatorDemo如下:

using System;
using System.IO;
using System.Net;
using System.Text;
using System.Collections.Specialized; // For NameValueCollection, though not directly used in this version, good to keep for potential expansion.
using System.Security.Cryptography; // For HMACSHA256, SHA256Managed
using System.Globalization; // For CultureInfo.InvariantCulture
using System.Collections.Generic; // For SortedList
using System.Linq; // For string.Join and other LINQ operations
using System.Xml.Linq;
namespace MinioHttpOperatorDemo
{
    /// <summary>
    /// MinIO HTTP 操作類(lèi),適用于 .NET Framework 4.0 環(huán)境,不依賴(lài) MinIO SDK。
    /// 使用 HttpWebRequest 和 HttpWebResponse 進(jìn)行文件上傳、下載和刪除操作。
    /// 已加入 AWS Signature Version 4 認(rèn)證的簡(jiǎn)化實(shí)現(xiàn),并進(jìn)行了進(jìn)一步的完善。
    /// </summary>
    public class MinioHttpOperator
    {
        private readonly string _minioEndpoint; // MinIO 服務(wù)器的端點(diǎn)地址,例如: http://localhost:9000
        private readonly string _accessKey;     // MinIO Access Key
        private readonly string _secretKey;     // MinIO Secret Key
        private readonly string _region;        // S3 兼容 API 需要的區(qū)域,MinIO 通常用 "us-east-1"
        private readonly string _service;       // S3 兼容 API 需要的服務(wù)名稱(chēng),通常是 "s3"
        /// <summary>
        /// 構(gòu)造函數(shù),初始化 MinIO 操作器。
        /// </summary>
        /// <param name="minioEndpoint">MinIO 服務(wù)器的 URL,例如 "http://localhost:9000"</param>
        /// <param name="accessKey">Access Key,用于認(rèn)證。</param>
        /// <param name="secretKey">Secret Key,用于認(rèn)證。</param>
        /// <param name="region">S3 兼容 API 需要的區(qū)域,默認(rèn)為 "us-east-1"。</param>
        /// <param name="service">S3 兼容 API 需要的服務(wù)名稱(chēng),默認(rèn)為 "s3"。</param>
        public MinioHttpOperator(string minioEndpoint, string accessKey, string secretKey, string region = "cn-north-1", string service = "s3")
        {
            // 移除末尾的斜杠,確保 URL 格式正確
            _minioEndpoint = minioEndpoint.TrimEnd('/');
            _accessKey = accessKey;
            _secretKey = secretKey;
            _region = region;
            _service = service;
            if (string.IsNullOrEmpty(_accessKey) || string.IsNullOrEmpty(_secretKey))
            {
                // 拋出異常而不是警告,因?yàn)闆](méi)有憑據(jù)就無(wú)法認(rèn)證
                throw new ArgumentNullException("AccessKey 和 SecretKey 不能為空,因?yàn)樾枰M(jìn)行認(rèn)證。");
            }
        }
        /// <summary>
        /// 上傳文件到 MinIO。
        /// </summary>
        /// <param name="bucketName">目標(biāo)桶的名稱(chēng)。</param>
        /// <param name="objectName">在桶中保存的對(duì)象名稱(chēng)(包含路徑,例如 "myfolder/myfile.txt")。</param>
        /// <param name="filePath">本地待上傳文件的完整路徑。</param>
        /// <param name="contentType">文件的 MIME 類(lèi)型,例如 "application/octet-stream"、"image/jpeg"、"text/plain"。</param>
        /// <returns>如果上傳成功返回 true,否則返回 false。</returns>
        public bool UploadFile(string bucketName, string objectName, string filePath, string contentType = "application/octet-stream")
        {
            try
            {
                if (!File.Exists(filePath))
                {
                    Console.WriteLine($"錯(cuò)誤:文件未找到,路徑:{filePath}");
                    return false;
                }
                string url = $"{_minioEndpoint}/{bucketName}/{objectName}";
                HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
                request.Method = "PUT";
                request.ContentType = contentType;
                // 計(jì)算文件內(nèi)容的 SHA256 哈希值
                string contentHash;
                using (FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
                {
                    using (SHA256 sha256 = new SHA256Managed())
                    {
                        byte[] hashBytes = sha256.ComputeHash(fileStream);
                        contentHash = BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
                    }
                    fileStream.Position = 0; // 重置流位置以便后續(xù)讀取
                    request.ContentLength = fileStream.Length; // 設(shè)置請(qǐng)求內(nèi)容長(zhǎng)度
                    // 簽名請(qǐng)求
                    SignRequest(request, bucketName, objectName, contentHash);
                    // 獲取請(qǐng)求流并寫(xiě)入文件內(nèi)容
                    using (Stream requestStream = request.GetRequestStream())
                    {
                        byte[] buffer = new byte[4096]; // 4KB 緩沖區(qū)
                        int bytesRead;
                        while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) > 0)
                        {
                            requestStream.Write(buffer, 0, bytesRead);
                        }
                    }
                }
                using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
                {
                    if (response.StatusCode == HttpStatusCode.OK)
                    {
                        Console.WriteLine($"成功上傳文件 {objectName} 到桶 {bucketName}。");
                        return true;
                    }
                    else
                    {
                        Console.WriteLine($"上傳文件 {objectName} 失敗。狀態(tài)碼:{response.StatusCode}");
                        return false;
                    }
                }
            }
            catch (WebException webEx)
            {
                HandleWebException(webEx, "上傳");
                return false;
            }
            catch (Exception ex)
            {
                Console.WriteLine($"上傳時(shí)發(fā)生未知錯(cuò)誤:{ex.Message}");
                return false;
            }
        }
        /// <summary>
        /// 從 MinIO 下載文件。
        /// </summary>
        /// <param name="bucketName">源桶的名稱(chēng)。</param>
        /// <param name="objectName">要下載的對(duì)象名稱(chēng)。</param>
        /// <param name="savePath">本地保存文件的完整路徑。</param>
        /// <returns>如果下載成功返回 true,否則返回 false。</returns>
        public bool DownloadFile(string bucketName, string objectName, string savePath)
        {
            try
            {
                string url = $"{_minioEndpoint}/{bucketName}/{objectName}";
                HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
                request.Method = "GET";
                // 對(duì)于 GET 請(qǐng)求,payload hash 是固定的空字符串的 SHA256 哈希
                string contentHash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; // SHA256("")
                SignRequest(request, bucketName, objectName, contentHash);
                using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
                {
                    if (response.StatusCode == HttpStatusCode.OK)
                    {
                        using (Stream responseStream = response.GetResponseStream())
                        using (FileStream fileStream = new FileStream(savePath, FileMode.Create, FileAccess.Write))
                        {
                            byte[] buffer = new byte[4096];
                            int bytesRead;
                            while ((bytesRead = responseStream.Read(buffer, 0, buffer.Length)) > 0)
                            {
                                fileStream.Write(buffer, 0, bytesRead);
                            }
                        }
                        Console.WriteLine($"成功下載文件 {objectName}{savePath}。");
                        return true;
                    }
                    else
                    {
                        Console.WriteLine($"下載文件 {objectName} 失敗。狀態(tài)碼:{response.StatusCode}");
                        return false;
                    }
                }
            }
            catch (WebException webEx)
            {
                HandleWebException(webEx, "下載");
                return false;
            }
            catch (Exception ex)
            {
                Console.WriteLine($"下載時(shí)發(fā)生未知錯(cuò)誤:{ex.Message}");
                return false;
            }
        }
        /// <summary>
        /// 從 MinIO 刪除文件。
        /// </summary>
        /// <param name="bucketName">文件所在桶的名稱(chēng)。</param>
        /// <param name="objectName">要?jiǎng)h除的對(duì)象名稱(chēng)。</param>
        /// <returns>如果刪除成功返回 true,否則返回 false。</returns>
        public bool DeleteFile(string bucketName, string objectName)
        {
            try
            {
                string url = $"{_minioEndpoint}/{bucketName}/{objectName}";
                HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
                request.Method = "DELETE";
                // 對(duì)于 DELETE 請(qǐng)求,payload hash 是固定的空字符串的 SHA256 哈希
                string contentHash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; // SHA256("")
                SignRequest(request, bucketName, objectName, contentHash);
                using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
                {
                    // 成功的 DELETE 請(qǐng)求通常返回 204 No Content
                    if (response.StatusCode == HttpStatusCode.NoContent)
                    {
                        Console.WriteLine($"成功刪除文件 {objectName} 從桶 {bucketName}。");
                        return true;
                    }
                    else
                    {
                        Console.WriteLine($"刪除文件 {objectName} 失敗。狀態(tài)碼:{response.StatusCode}");
                        return false;
                    }
                }
            }
            catch (WebException webEx)
            {
                HandleWebException(webEx, "刪除");
                return false;
            }
            catch (Exception ex)
            {
                Console.WriteLine($"刪除時(shí)發(fā)生未知錯(cuò)誤:{ex.Message}");
                return false;
            }
        } 
        /// <summary>
        /// 處理 Web 請(qǐng)求異常并輸出詳細(xì)信息。
        /// </summary>
        /// <param name="webEx">WebException 實(shí)例。</param>
        /// <param name="operation">發(fā)生異常的操作名稱(chēng)(例如:"上傳"、"下載")。</param>
        private void HandleWebException(WebException webEx, string operation)
        {
            if (webEx.Response != null)
            {
                using (StreamReader reader = new StreamReader(webEx.Response.GetResponseStream()))
                {
                    string responseText = reader.ReadToEnd();
                    Console.WriteLine($"{operation}時(shí)發(fā)生 Web 異常:{webEx.Message}。狀態(tài)碼:{(int)((HttpWebResponse)webEx.Response).StatusCode}。響應(yīng)內(nèi)容:{responseText}");
                }
            }
            else
            {
                Console.WriteLine($"{operation}時(shí)發(fā)生 Web 異常:{webEx.Message}");
            }
        }
        /// <summary>
        /// 為 HttpWebRequest 簽名 AWS Signature Version 4 認(rèn)證頭部。
        /// 這是 AWS Signature Version 4 規(guī)范的簡(jiǎn)化實(shí)現(xiàn),旨在與 MinIO 兼容。
        /// </summary>
        /// <param name="request">要簽名的 HttpWebRequest 實(shí)例。</param>
        /// <param name="bucketName">桶名稱(chēng)。</param>
        /// <param name="objectName">對(duì)象名稱(chēng)。</param>
        /// <param name="contentHash">請(qǐng)求體的 SHA256 哈希值。</param>
        private void SignRequest(HttpWebRequest request, string bucketName, string objectName, string contentHash)
        {
            // --- 步驟 1: 創(chuàng)建 Canonical Request ---
            // 1.1 HTTP 方法
            string httpRequestMethod = request.Method;
            // 1.2 Canonical URI
            // 對(duì)象名必須進(jìn)行 URI 編碼,但斜杠 (/) 作為路徑分隔符不能被編碼。
            // Uri.EscapeDataString 會(huì)編碼 '/' 為 '%2F',需要將其替換回來(lái)。
            // 確保 objectName 不以斜杠開(kāi)頭,因?yàn)?canonicalUri 會(huì)添加一個(gè)。
            string cleanedObjectName = objectName.StartsWith("/") ? objectName.Substring(1) : objectName;
            string encodedObjectName = Uri.EscapeDataString(cleanedObjectName).Replace("%2F", "/");
            string canonicalUri = $"/{bucketName}/{encodedObjectName}";
            // 1.3 Canonical Query String (本示例不處理查詢(xún)參數(shù),因此為空)
            string canonicalQueryString = "";
            // 1.4 Canonical Headers
            // 頭部名稱(chēng)必須小寫(xiě),并按字典序排序。
            // 頭部值必須去除前導(dǎo)/尾隨空格,多個(gè)空格替換為單個(gè)空格。
            // Host 頭部必須包含端口(如果是非默認(rèn)端口)。
            var headersToSign = new SortedList<string, string>();
            // Host 頭部
            string hostHeaderValue = request.RequestUri.Host;
            if (!request.RequestUri.IsDefaultPort)
            {
                hostHeaderValue += ":" + request.RequestUri.Port;
            }
            headersToSign.Add("host", hostHeaderValue);
            // x-amz-content-sha256 頭部
            headersToSign.Add("x-amz-content-sha256", contentHash);
            // x-amz-date 頭部
            DateTime requestDateTime = DateTime.UtcNow;
            string amzDate = requestDateTime.ToString("yyyyMMddTHHmmssZ", CultureInfo.InvariantCulture);
            headersToSign.Add("x-amz-date", amzDate);
            // Content-Type 頭部 (僅用于 PUT/POST 請(qǐng)求)
            if (request.Method == "PUT" || request.Method == "POST")
            {
                string actualContentType = request.ContentType;
                if (string.IsNullOrEmpty(actualContentType))
                {
                    actualContentType = "application/octet-stream"; // 簽名時(shí)使用的默認(rèn) Content-Type
                }
                headersToSign.Add("content-type", actualContentType);
            }
            // 構(gòu)建 canonicalHeaders 字符串
            StringBuilder canonicalHeadersBuilder = new StringBuilder();
            foreach (var header in headersToSign)
            {
                canonicalHeadersBuilder.AppendFormat(CultureInfo.InvariantCulture, "{0}:{1}\n", header.Key, header.Value.Trim()); // trim header values
            }
            string canonicalHeaders = canonicalHeadersBuilder.ToString();
            // 1.5 Signed Headers
            // 包含在規(guī)范化頭部中所有頭部名稱(chēng)的列表,小寫(xiě),按字典序排序,用分號(hào)分隔。
            string signedHeaders = string.Join(";", headersToSign.Keys.ToArray());
            // 1.6 Payload Hash (已在方法參數(shù)中提供)
            // 1.7 組合 Canonical Request
            string canonicalRequest = string.Format(CultureInfo.InvariantCulture,
                "{0}\n{1}\n{2}\n{3}\n{4}\n{5}",
                httpRequestMethod,
                canonicalUri,
                canonicalQueryString,
                canonicalHeaders, // 注意這里已經(jīng)包含了末尾的換行符
                signedHeaders,
                contentHash);
            // --- 步驟 2: 創(chuàng)建 String to Sign ---
            // Algorithm
            string algorithm = "AWS4-HMAC-SHA256";
            // Credential Scope
            string dateStamp = requestDateTime.ToString("yyyyMMdd", CultureInfo.InvariantCulture);
            string credentialScope = string.Format(CultureInfo.InvariantCulture,
                "{0}/{1}/{2}/aws4_request",
                dateStamp,
                _region,
                _service);
            // Hash of Canonical Request
            string hashedCanonicalRequest = ToHex(Hash(Encoding.UTF8.GetBytes(canonicalRequest)));
            // 組合 String to Sign
            string stringToSign = string.Format(CultureInfo.InvariantCulture,
                "{0}\n{1}\n{2}\n{3}",
                algorithm,
                amzDate,
                credentialScope,
                hashedCanonicalRequest);
            // --- 步驟 3: 計(jì)算簽名 ---
            // Signing Key 派生
            byte[] kSecret = Encoding.UTF8.GetBytes("AWS4" + _secretKey);
            byte[] kDate = HmacSha256(kSecret, dateStamp);
            byte[] kRegion = HmacSha256(kDate, _region);
            byte[] kService = HmacSha256(kRegion, _service);
            byte[] kSigning = HmacSha256(kService, "aws4_request");
            // 計(jì)算最終簽名
            byte[] signatureBytes = HmacSha256(kSigning, stringToSign);
            string signature = ToHex(signatureBytes);
            // --- 步驟 4: 添加 Authorization 頭部 ---
            string authorizationHeader = string.Format(CultureInfo.InvariantCulture,
                "{0} Credential={1}/{2}, SignedHeaders={3}, Signature={4}",
                algorithm,
                _accessKey,
                credentialScope,
                signedHeaders,
                signature);
            request.Headers["Authorization"] = authorizationHeader;
            // 設(shè)置 x-amz-date 頭部(如果尚未設(shè)置)
            request.Headers["x-amz-date"] = amzDate;
            // 設(shè)置 x-amz-content-sha256 頭部(如果尚未設(shè)置)
            request.Headers["x-amz-content-sha256"] = contentHash;
        }
        /// <summary>
        /// 計(jì)算字節(jié)數(shù)組的 SHA256 哈希值。
        /// </summary>
        private static byte[] Hash(byte[] bytes)
        {
            using (SHA256 sha256 = new SHA256Managed())
            {
                return sha256.ComputeHash(bytes);
            }
        }
        /// <summary>
        /// 計(jì)算 HMAC-SHA256 哈希值。
        /// </summary>
        private static byte[] HmacSha256(byte[] key, string data)
        {
            using (HMACSHA256 hmac = new HMACSHA256(key))
            {
                return hmac.ComputeHash(Encoding.UTF8.GetBytes(data));
            }
        }
        /// <summary>
        /// 將字節(jié)數(shù)組轉(zhuǎn)換為十六進(jìn)制字符串。
        /// </summary>
        private static string ToHex(byte[] bytes)
        {
            return BitConverter.ToString(bytes).Replace("-", "").ToLowerInvariant();
        }
    }
}

測(cè)試代碼如下:

using System;
using System.IO;
using System.Threading;
namespace MinioHttpOperatorDemo
{
    class Program
    {
        static void Main(string[] args)
        {   // 替換為您的 MinIO 實(shí)例的地址
            string minioEndpoint = "http://127.xxx.xxx.xxx:9000";
            // 如果您的 MinIO 實(shí)例需要認(rèn)證,請(qǐng)?jiān)谶@里提供您的 AccessKey 和 SecretKey。
            // 例如:
            string accessKey = "accessKey ";
            string secretKey = "secretKey ";
            MinioHttpOperator minioOperator = new MinioHttpOperator(minioEndpoint, accessKey, secretKey);
            // 如果 MinIO 允許匿名訪問(wèn),則無(wú)需提供 AccessKey 和 SecretKey
            //MinioHttpOperator minioOperator = new MinioHttpOperator(minioEndpoint);
            // 確保此桶在 MinIO 中存在或 MinIO 服務(wù)器允許自動(dòng)創(chuàng)建桶。
            string bucketName = "bucketName ";
            string testFolder = "test";
            // --- 準(zhǔn)備多個(gè)測(cè)試文件 ---
            string localFilePath1 = Path.Combine(Path.GetTempPath(), "testfile1.txt");
            string localFilePath2 = Path.Combine(Path.GetTempPath(), "testfile2.jpg"); // 模擬圖片文件
            string minioObjectName1 = $"{testFolder}/document1.txt";
            string minioObjectName2 = $"{testFolder}/image.jpg";
            string content1 = "This is the content for document one.";
            byte[] content2 = new byte[1024]; // 模擬一個(gè)1KB的二進(jìn)制數(shù)據(jù)作為圖片內(nèi)容
            new Random().NextBytes(content2); // 填充隨機(jī)字節(jié)
            try
            {
                File.WriteAllText(localFilePath1, content1);
                File.WriteAllBytes(localFilePath2, content2);
                Console.WriteLine($"已在本地創(chuàng)建測(cè)試文件:{localFilePath1}{localFilePath2}");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"創(chuàng)建測(cè)試文件失敗:{ex.Message}");
                Console.WriteLine("請(qǐng)檢查文件路徑和權(quán)限。程序?qū)⑼顺觥?);
                Console.ReadKey();
                return;
            }
            Console.WriteLine("\n--- 開(kāi)始 MinIO 批量操作 ---");
            // --- 上傳第一個(gè)文件 ---
            Console.WriteLine($"\n嘗試上傳文件:{localFilePath1}{minioEndpoint}/{bucketName}/{minioObjectName1}...");
            if (minioOperator.UploadFile(bucketName, minioObjectName1, localFilePath1, "text/plain"))
            {
                Console.WriteLine("文件上傳成功。");
            }
            else
            {
                Console.WriteLine("文件上傳失敗。");
            }
            Thread.Sleep(1000); // 稍作等待
            // --- 上傳第二個(gè)文件 ---
            Console.WriteLine($"\n嘗試上傳文件:{localFilePath2}{minioEndpoint}/{bucketName}/{minioObjectName2}...");
            if (minioOperator.UploadFile(bucketName, minioObjectName2, localFilePath2, "image/jpeg"))
            {
                Console.WriteLine("文件上傳成功。");
            }
            else
            {
                Console.WriteLine("文件上傳失敗。");
            }
            Thread.Sleep(1000); // 稍作等待
            Console.WriteLine("\n----------------------------------------");
            // --- 下載第一個(gè)文件 ---
            string downloadSavePath1 = Path.Combine(Path.GetTempPath(), "downloaded_document1.txt");
            Console.WriteLine($"\n嘗試從 {minioEndpoint}/{bucketName}/{minioObjectName1} 下載文件到 {downloadSavePath1}...");
            if (minioOperator.DownloadFile(bucketName, minioObjectName1, downloadSavePath1))
            {
                Console.WriteLine("文件下載成功。");
                try
                {
                    Console.WriteLine($"下載文件的內(nèi)容:{File.ReadAllText(downloadSavePath1)}");
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"讀取下載文件內(nèi)容失敗:{ex.Message}");
                }
            }
            else
            {
                Console.WriteLine("文件下載失敗。");
            }
            Thread.Sleep(1000); // 稍作等待
            // --- 嘗試下載一個(gè)不存在的文件 ---
            string nonExistentObject = $"{testFolder}/nonexistent.pdf";
            string downloadNonExistentPath = Path.Combine(Path.GetTempPath(), "nonexistent.pdf");
            Console.WriteLine($"\n嘗試下載不存在的文件:{nonExistentObject}...");
            if (!minioOperator.DownloadFile(bucketName, nonExistentObject, downloadNonExistentPath))
            {
                Console.WriteLine("下載不存在的文件失敗(預(yù)期結(jié)果)。");
            }
            Thread.Sleep(1000); // 稍作等待
            Console.WriteLine("\n----------------------------------------");
            // --- 刪除第一個(gè)文件 ---
            Console.WriteLine($"\n嘗試從 {minioEndpoint}/{bucketName} 刪除文件 {minioObjectName1}...");
            if (minioOperator.DeleteFile(bucketName, minioObjectName1))
            {
                Console.WriteLine("文件刪除成功。");
            }
            else
            {
                Console.WriteLine("文件刪除失敗。");
            }
            Thread.Sleep(1000); // 稍作等待
            // --- 刪除第二個(gè)文件 ---
            Console.WriteLine($"\n嘗試從 {minioEndpoint}/{bucketName} 刪除文件 {minioObjectName2}...");
            if (minioOperator.DeleteFile(bucketName, minioObjectName2))
            {
                Console.WriteLine("文件刪除成功。");
            }
            else
            {
                Console.WriteLine("文件刪除失敗。");
            }
            Console.WriteLine("\n--- MinIO 批量操作結(jié)束 ---");
            // 清理本地創(chuàng)建的測(cè)試文件
            try
            {
                if (File.Exists(localFilePath1))
                {
                    File.Delete(localFilePath1);
                    Console.WriteLine($"已清理本地測(cè)試文件:{localFilePath1}");
                }
                if (File.Exists(localFilePath2))
                {
                    File.Delete(localFilePath2);
                    Console.WriteLine($"已清理本地測(cè)試文件:{localFilePath2}");
                }
                if (File.Exists(downloadSavePath1))
                {
                    File.Delete(downloadSavePath1);
                    Console.WriteLine($"已清理本地下載文件:{downloadSavePath1}");
                }
                if (File.Exists(downloadNonExistentPath))
                {
                    File.Delete(downloadNonExistentPath);
                    Console.WriteLine($"已清理本地下載文件:{downloadNonExistentPath}");
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"清理本地文件失敗:{ex.Message}");
            }
            Console.WriteLine("\n按任意鍵退出程序。");
            Console.ReadKey();
        }
    }
}

轉(zhuǎn)自https://www.cnblogs.com/hanfan/p/19010378


該文章在 2025/8/1 9:41:45 編輯過(guò)
關(guān)鍵字查詢(xún)
相關(guān)文章
正在查詢(xún)...
點(diǎn)晴ERP是一款針對(duì)中小制造業(yè)的專(zhuān)業(yè)生產(chǎn)管理軟件系統(tǒng),系統(tǒng)成熟度和易用性得到了國(guó)內(nèi)大量中小企業(yè)的青睞。
點(diǎn)晴PMS碼頭管理系統(tǒng)主要針對(duì)港口碼頭集裝箱與散貨日常運(yùn)作、調(diào)度、堆場(chǎng)、車(chē)隊(duì)、財(cái)務(wù)費(fèi)用、相關(guān)報(bào)表等業(yè)務(wù)管理,結(jié)合碼頭的業(yè)務(wù)特點(diǎn),圍繞調(diào)度、堆場(chǎng)作業(yè)而開(kāi)發(fā)的。集技術(shù)的先進(jìn)性、管理的有效性于一體,是物流碼頭及其他港口類(lèi)企業(yè)的高效ERP管理信息系統(tǒng)。
點(diǎn)晴WMS倉(cāng)儲(chǔ)管理系統(tǒng)提供了貨物產(chǎn)品管理,銷(xiāo)售管理,采購(gòu)管理,倉(cāng)儲(chǔ)管理,倉(cāng)庫(kù)管理,保質(zhì)期管理,貨位管理,庫(kù)位管理,生產(chǎn)管理,WMS管理系統(tǒng),標(biāo)簽打印,條形碼,二維碼管理,批號(hào)管理軟件。
點(diǎn)晴免費(fèi)OA是一款軟件和通用服務(wù)都免費(fèi),不限功能、不限時(shí)間、不限用戶(hù)的免費(fèi)OA協(xié)同辦公管理系統(tǒng)。
Copyright 2010-2025 ClickSun All Rights Reserved

黄频国产免费高清视频,久久不卡精品中文字幕一区,激情五月天AV电影在线观看,欧美国产韩国日本一区二区
中文字幕乱在线伦视频日韩 | 日韩网站免费大片在线看 | 亚洲手机在线观看看片 | 伊人青青久精品 | 亚洲AV成人精品一区二区三区 | 日韩亚洲欧美国产动漫在线观看 |