59.【.NET8 實戰--孢子記賬--從單體到微服務--轉向微服務】--新增功能--MinIO對象存儲服務

在孢子記賬中我們需要存儲用戶的頭像、賬單的圖片等文件,這些文件的存儲我們可以使用MinIO對象存儲服務,
MinIO提供了高性能、可擴展的對象存儲解決方案,能夠幫助我們輕松管理這些文件資源。通過MinIO,我們可以將用戶上傳的圖片文件安全地存儲在云端,并且可以隨時通過HTTP訪問這些資源。

一、關于MinIO

1. 什么是MinIO

MinIO是一個功能強大的開源對象存儲服務器,基于Apache License v2.0開源協議發布。作為一個現代化的存儲解決方案,它為用戶提供了企業級的高可用性、無限的水平擴展能力以及卓越的性能表現。MinIO采用Go語言開發,具有輕量級、易部署的特點,同時提供與Amazon S3兼容的API接口,使得它能夠無縫對接現有的云存儲生態系統。

在架構設計上,MinIO采用分布式架構,支持多節點部署和數據復制,確保數據的可靠性和容錯能力。它使用糾刪碼技術來保護數據,即使在多個節點故障的情況下也能保證數據的完整性。MinIO的性能表現尤為出色,單個節點就能達到數GB/s的讀寫速度,并且隨著節點的增加,性能可以線性提升。

MinIO的應用場景十分廣泛,特別適合現代云原生應用的開發需求。在大數據分析領域,它可以作為數據湖的存儲后端,支持Hadoop、Spark等大數據框架的直接訪問。在機器學習和AI訓練場景中,MinIO能夠高效處理大量的訓練數據集。對于需要處理圖片、視頻等多媒體文件的應用,MinIO提供了快速的上傳下載能力和便捷的文件管理功能。此外,它還可以作為備份存儲、日志存儲等基礎設施的重要組成部分。

2. 安裝部署MinIO

MinIO的安裝部署非常簡單,我們可以在官方網站下載對應操作系統的安裝包,然后按照安裝指南進行安裝。MinIO支持在Linux、Windows和macOS等操作系統上運行,同時也提供了Docker鏡像,方便在容器化環境中部署。我們以Docker為例,來看一下如何部署。

首先,我們需要拉取MinIO的Docker鏡像:

docker pull minio/minio

這個命令會拉取最新的MinIO鏡像文件,之后使用docker images命令查看MinIO的鏡像是否已經已拉取到本地:

docker images

我們可以看到MinIO的鏡像文件已經被成功拉取到本地。

接下來,我們要創建一個數據目錄,用來存儲MinIO的文件數據:

mkdir -p $HOME/minio/data
mkdir -p $HOME/minio/config

執行這個命令后,將在當前用戶主目錄下創建路徑 minio/dataminio/config,這兩個路徑分別用來存儲MinIO的文件數據和配置文件,后續用來存儲MinIO的文件數據,并且這個兩個目錄將會掛在到MinIO容器中。

接下來,我們要創建MinIO的容器:

docker run -d --name minio \-p 9000:9000 -p 9001:9001 \-e "MINIO_ACCESS_KEY=minioadmin" \-e "MINIO_SECRET_KEY=minioadmin" \-v $HOME/minio/data:/data \-v $HOME/minio/config:/root/.minio \minio/minio server /data --console-address ":9001"

這個命令會創建一個名為minio的容器,將MinIO的文件數據存儲在$HOME/minio/data目錄下,將MinIO的配置文件存儲在$HOME/minio/config目錄下,并且創建了一個名為minioadmin的用戶,用戶的訪問密鑰為minioadmin。MinIO的API端口為9000,控制臺端口為9001。

我們在瀏覽器中訪問http://YOUR_IP:9001,使用minioadmin用戶登錄MinIO控制臺,就看到了MinIO的首頁。最后,我們創建一個名稱為sporeaccounting的存儲桶,作為孢子記賬項目的存儲根目錄。

二、創建資源微服務

在這一小節,我們將創建資源微服務SP.ResourceService,這是一個專門為孢子記賬項目提供文件資源管理的微服務。資源微服務將負責處理所有與文件相關的操作,包括用戶頭像和賬單圖片等資源的管理。通過與MinIO對象存儲的集成,我們可以實現安全可靠的文件存儲和訪問機制。該服務將提供完整的文件生命周期管理功能,允許用戶上傳文件到MinIO存儲系統,獲取文件的訪問URL以便前端展示,在不需要時刪除文件釋放存儲空間。為了優化文件上傳體驗和提高性能,我們還將實現基于預簽名URL的前端直傳機制,客戶端可以獲取臨時的上傳憑證,直接將文件上傳到MinIO存儲,而無需經過應用服務器中轉。在文件上傳完成后,客戶端會通知資源服務進行確認,以確保文件上傳的完整性和有效性。這種設計不僅能提供良好的用戶體驗,還能有效減輕應用服務器的負載。

2.1 上傳文件

我們首先實現上傳文件功能,在Service文件夾下新建OSS服務接口IOssService,在這個接口中添加上傳文件方法UploadAsync,代碼如下:

/// <summary>
/// 上傳文件
/// </summary>
/// <param name="file">文件</param>
/// <param name="isPublic">是否公開</param>
/// <param name="ct">取消令牌</param>
Task UploadAsync(IFormFile file, bool isPublic = true, CancellationToken ct = default);

UploadAsync方法是一個異步方法,用于將文件上傳到MinIO對象存儲服務。該方法接收IFormFile類型的file參數用于處理上傳的文件,IFormFile是ASP.NET Core中用于處理HTTP請求中文件的接口,包含了文件的基本信息(如文件名、大小、內容類型等)和文件流;isPublic參數為bool類型且默認值為true,用于控制上傳文件的訪問權限,true表示文件可以通過URL直接訪問,false表示需要授權才能訪問;ct參數為CancellationToken類型且默認值為default,這是.NET中用于協調異步操作取消的標準機制,可以在長時間運行的文件上傳過程中實現取消功能。

在實現類中,我們將使用MinIO的SDK來完成實際的文件上傳工作。在Service/Impl文件夾下新建MinIo的實現類。

/// <summary>
/// MinIO 客戶端
/// </summary>
private readonly IMinioClient _client;/// <summary>
/// MinIO 配置選項
/// </summary>
private readonly IOptions<MinioOptions> _options;/// <summary>
/// 日志記錄器
/// </summary>
private readonly ILogger<MinioOssService> _logger;/// <summary>
/// 數據庫上下文
/// </summary>
private readonly ResourceServiceDbContext _dbContext;/// <summary>
/// 構造函數
/// </summary>
/// <param name="options"></param>
/// <param name="logger"></param>
/// <param name="dbContext"></param>
public MinioOssService(IOptions<MinioOptions> options, ILogger<MinioOssService> logger,ResourceServiceDbContext dbContext)
{_options = options;_logger = logger;_dbContext = dbContext;try{// 驗證配置ValidateConfiguration(options.Value);// 處理端點URL格式 - MinIO客戶端期望的是主機名和端口,而不是完整的URLvar endpoint = ProcessEndpoint(options.Value.Endpoint);// 初始化 MinIO 客戶端var clientBuilder = new MinioClient().WithEndpoint(endpoint).WithCredentials(options.Value.AccessKey, options.Value.SecretKey);if (options.Value.WithSSL){clientBuilder = clientBuilder.WithSSL();}// 構建客戶端_client = clientBuilder.Build();}catch (Exception ex){_logger.LogError(ex, "構建客戶端失敗");throw;}
}/// <summary>
/// 上傳文件
/// </summary>
/// <param name="file">文件</param>
/// <param name="isPublic">是否公開</param>
/// <param name="ct">取消令牌</param>
/// <returns></returns>
public async Task UploadAsync(IFormFile file, bool isPublic = true, CancellationToken ct = default)
{using var streamRead = file.OpenReadStream();var objectName = $"{DateTime.UtcNow:yyyy/MM/dd}/{Guid.NewGuid():N}{Path.GetExtension(file.FileName)}";var bucket = isPublic ? _options.Value.PublicBucket : _options.Value.PrivateBucket;await EnsureBucketAsync(bucket, ct);var putArgs = new PutObjectArgs();// 計算大小(若不可Seek,先緩沖)long size;if (streamRead.CanSeek){size = streamRead.Length - streamRead.Position;putArgs.WithStreamData(streamRead);}else{var ms = new MemoryStream();await streamRead.CopyToAsync(ms, ct);ms.Position = 0;var stream = streamRead;stream = ms;size = ms.Length;putArgs.WithStreamData(stream);}putArgs.WithBucket(bucket).WithObject(objectName).WithObjectSize(size).WithContentType(file.ContentType);await _client.PutObjectAsync(putArgs, ct);Files fileInfo = new Files{ObjectName = objectName,IsPublic = isPublic,Size = size,ContentType = file.ContentType,OriginalName = file.FileName};SettingCommProperty.Create(fileInfo);_dbContext.Files.Add(fileInfo);await _dbContext.SaveChangesAsync(ct);
}

首先,MinioOssService 類的構造函數接收了三個重要的依賴:MinIO配置選項、日志記錄器和數據庫上下文。在構造函數中,我們首先驗證配置的有效性,然后處理MinIO的端點URL格式,因為MinIO客戶端需要的是主機名和端口,而不是完整的URL。接著使用Builder模式構建MinIO客戶端實例,如果配置了SSL,則啟用安全連接。

UploadAsync方法中,我們實現了文件上傳的核心邏輯。該方法首先打開文件流,并根據當前日期和GUID生成一個唯一的對象名稱,這樣可以避免文件名沖突。根據isPublic參數選擇使用公開或私有存儲桶,并確保存儲桶存在。文件上傳的過程中,我們需要特別處理文件流的大小計算。如果流支持Seek操作,我們可以直接獲取其長度;如果不支持,則需要先將內容復制到內存流中進行緩沖。這樣的處理確保了上傳過程的可靠性。上傳參數的設置也很關鍵,我們通過PutObjectArgs設置了存儲桶名稱、對象名稱、文件大小和內容類型等必要信息。使用MinIO客戶端的PutObjectAsync方法執行實際的上傳操作。

最后,我們將文件信息保存到數據庫中。創建Files實體對象,設置對象名稱、公開狀態、大小、內容類型和原始文件名等信息,并通過SettingCommProperty.Create設置通用屬性(如創建時間、創建者等)。最后將實體添加到數據庫上下文并保存更改。

在代碼中我們調用了ProcessEndpointValidateConfigurationEnsureBucketAsync 三個方法,它們都是私有方法,用來輔助文件上傳功能,我們來看一下這三個方法的代碼:

/// <summary>
/// 確保桶存在
/// </summary>
/// <param name="bucket">桶名稱</param>
/// <param name="ct">取消令牌</param>
/// <returns></returns>
private async Task EnsureBucketAsync(string bucket, CancellationToken ct)
{var exists = await _client.BucketExistsAsync(new BucketExistsArgs().WithBucket(bucket), ct);if (!exists){await _client.MakeBucketAsync(new MakeBucketArgs().WithBucket(bucket), ct);_logger.LogInformation("Created bucket: {Bucket}", bucket);// 如果是公共桶,設置為公共訪問策略if (bucket == _options.Value.PublicBucket){await SetPublicBucketPolicyAsync(bucket, ct);_logger.LogInformation("Set public access policy for bucket: {Bucket}", bucket);}}
}/// <summary>
/// 為桶設置公共訪問策略
/// </summary>
/// <param name="bucket">桶名稱</param>
/// <param name="ct">取消令牌</param>
/// <returns></returns>
private async Task SetPublicBucketPolicyAsync(string bucket, CancellationToken ct)
{try{// 創建公共訪問策略JSONvar policy = new{Version = "2012-10-17",Statement = new[]{new{Effect = "Allow",Principal = new { AWS = new[] { "*" } },Action = new[] { "s3:GetObject" },Resource = new[] { $"arn:aws:s3:::{bucket}/*" }}}};var policyJson = JsonSerializer.Serialize(policy, new JsonSerializerOptions{PropertyNamingPolicy = JsonNamingPolicy.CamelCase,WriteIndented = false});// 使用SetPolicyAsync方法設置桶策略var setPolicyArgs = new SetPolicyArgs().WithBucket(bucket).WithPolicy(policyJson);await _client.SetPolicyAsync(setPolicyArgs, ct);}catch (Exception ex){// 不拋出異常,因為桶已經創建成功,只是策略設置失敗_logger.LogError(ex, "為桶設置公共訪問策略失敗: {Bucket}。桶仍可用,但對象無法通過URL直接訪問。", bucket);}
}/// <summary>
/// 驗證MinIO配置
/// </summary>
/// <param name="options">MinIO配置選項</param>
/// <exception cref="ArgumentException">配置無效時拋出異常</exception>
private void ValidateConfiguration(MinioOptions options)
{if (string.IsNullOrWhiteSpace(options.Endpoint)){throw new ArgumentException("MinIO 端點不能為空");}if (string.IsNullOrWhiteSpace(options.AccessKey)){throw new ArgumentException("MinIO AccessKey 不能為空");}if (string.IsNullOrWhiteSpace(options.SecretKey)){throw new ArgumentException("MinIO SecretKey 不能為空");}if (string.IsNullOrWhiteSpace(options.PublicBucket)){throw new ArgumentException("MinIO 公共桶不能為空");}if (string.IsNullOrWhiteSpace(options.PrivateBucket)){throw new ArgumentException("MinIO 私有桶不能為空");}
}/// <summary>
/// 處理端點URL格式
/// </summary>
/// <param name="endpoint">原始端點</param>
/// <returns>處理后的端點</returns>
private string ProcessEndpoint(string endpoint)
{// 如果端點包含協議前綴,需要移除if (endpoint.StartsWith("http://", StringComparison.OrdinalIgnoreCase)){endpoint = endpoint.Substring("http://".Length);}else if (endpoint.StartsWith("https://", StringComparison.OrdinalIgnoreCase)){endpoint = endpoint.Substring("https://".Length);}// 移除末尾的斜杠endpoint = endpoint.TrimEnd('/');return endpoint;
}

上述代碼是上傳文件功能的關鍵私有輔助方法,EnsureBucketAsync方法負責確保存儲桶的存在和正確配置。方法首先檢查指定的存儲桶是否存在,如果不存在則創建新的存儲桶。對于公共訪問的存儲桶(由配置中的PublicBucket指定),方法會額外調用SetPublicBucketPolicyAsync來設置適當的訪問策略。這個方法保證了存儲桶在使用前已經正確創建和配置。

SetPublicBucketPolicyAsync方法實現了為公共存儲桶設置訪問策略的功能。方法創建了一個符合AWS S3標準的策略JSON,允許所有用戶對存儲桶中的對象進行GetObject操作。策略使用JSON序列化,并通過MinIO客戶端的SetPolicyAsync方法應用到存儲桶。這個方法采用了容錯設計,即使策略設置失敗,也不會拋出異常而是記錄錯誤日志,這樣可以確保上傳功能仍然可用,只是可能無法通過URL直接訪問文件。

ValidateConfiguration方法用于驗證MinIO配置的完整性和有效性。它檢查了包括端點(Endpoint)、訪問密鑰(AccessKey)、密鑰(SecretKey)以及公共和私有存儲桶名稱在內的所有必要配置項。如果任何配置項為空或未設置,方法會拋出ArgumentException異常,這種嚴格的配置驗證確保了系統在啟動時就能發現配置問題,避免運行時出現意外錯誤。

ProcessEndpoint方法處理MinIO服務端點URL的格式化。這個方法的存在是因為MinIO客戶端需要特定格式的端點地址,它期望得到的是主機名和端口,而不是完整的URL。該方法會移除URL中的協議前綴("http://“或"https://”)以及末尾的斜杠,確保端點地址符合MinIO客戶端的要求。

最后我們新建控制器FilesController,并新增Action Upload,代碼如下,代碼很簡單,這里就不再多講了。

/// <summary>
/// 上傳文件
/// </summary>
/// <param name="file"></param>
/// <param name="isPublic"></param>
/// <returns></returns>
[HttpPost("upload")]
[Consumes("multipart/form-data")]
public async Task<ActionResult> Upload(IFormFile file, [FromQuery] bool isPublic = true)
{await _oss.UploadAsync(file,isPublic);return Ok();
}
2.2 獲取文件URL

接下來我們實現獲取文件URL的功能。對于公開的文件,我們將返回一個可以直接訪問的URL;對于私有文件,我們將生成一個帶有時效性的預簽名URL。這樣可以確保文件訪問的安全性,同時為用戶提供便捷的訪問方式。讓我們看看具體的實現代碼:

// =================IOssService===================/// <summary>
/// 獲取文件URL
/// </summary>
/// <param name="fileId">文件id</param>
Task<string> GetUrlAsync(long fileId);// =================MinioOssService===================/// <summary>
/// 獲取文件URL
/// </summary>
/// <param name="fileId">文件id</param>
/// <returns></returns>
public async Task<string> GetUrlAsync(long fileId)
{// 查詢文件信息Files? file = _dbContext.Files.FirstOrDefault(f=>f.Id==fileId && f.IsDeleted== false);if (file == null){throw new NotFoundException("文件不存在");}string bucket = "";string objectName = file.ObjectName;if (file.IsPublic){bucket = _options.Value.PublicBucket;// 公開桶:返回直鏈var baseUrl = _options.Value.PublicBaseUrl?.TrimEnd('/');if (!string.IsNullOrWhiteSpace(baseUrl)){return $"{baseUrl}/{bucket}/{Uri.EscapeDataString(objectName)}";}// 若未配置 PublicBaseUrl,則退回到 MinIO 原始地址var scheme = _options.Value.WithSSL ? "https" : "http";return $"{scheme}://{_options.Value.Endpoint.TrimEnd('/')}/{bucket}/{Uri.EscapeDataString(objectName)}";}else{// 私有桶:返回預簽名bucket = _options.Value.PrivateBucket;var expirySeconds = _options.Value.PresignedExpireSeconds;var preArgs = new PresignedGetObjectArgs().WithBucket(bucket).WithObject(objectName).WithExpiry(expirySeconds);return await _client.PresignedGetObjectAsync(preArgs);}
}

GetUrlAsync方法是一個關鍵的文件訪問URL生成功能。該方法通過文件ID從數據庫查詢文件信息,實現了對公開和私有文件的不同訪問策略處理。方法首先會驗證文件的存在性和有效性,如果找不到文件或文件已被標記為刪除,會拋出NotFoundException異常來及時通知調用方。

在處理公開文件時,方法采用了靈活的URL生成策略。它優先使用配置中的PublicBaseUrl,這通常用于已配置CDN或自定義域名的場景。通過將PublicBaseUrl、存儲桶名稱和經過URL編碼的對象名稱組合,生成一個可直接訪問的完整URL。如果沒有配置PublicBaseUrl,方法會降級使用MinIO服務器的原始地址,根據WithSSL配置選擇合適的協議(http或https),確保在任何情況下都能生成有效的訪問地址。

對于私有文件的處理則采用了更安全的方式。方法使用MinIO客戶端的PresignedGetObjectAsync功能生成帶有訪問憑證的預簽名URL。這個URL具有時效性,其有效期由配置中的PresignedExpireSeconds控制。預簽名URL包含了必要的認證信息,確保只有獲得授權的用戶在規定時間內能夠訪問文件,既保證了便捷訪問,又不影響安全性。這種機制適合需要臨時授權訪問的場景,比如文件預覽或限時下載。

Tip:Controller 中的調用方式很簡單,就不再展示了,大家在項目的GitHub中查看完整代碼。

2.3 刪除文件

在資源管理中,刪除功能是不可或缺的一部分。當用戶不再需要某個文件時,我們需要同時清理MinIO存儲中的實際文件和數據庫中的文件記錄。這個功能需要謹慎實現,因為文件刪除是不可逆的操作。我們的實現會先驗證文件的存在性,然后依次執行存儲清理和數據庫更新操作,確保整個刪除過程的原子性和可靠性。讓我們來看看具體的實現代碼:

// =================IOssService===================/// <summary>
/// 刪除文件
/// </summary>
/// <param name="fileId">文件id</param>
/// <param name="ct">取消令牌</param>
Task DeleteAsync(long fileId, CancellationToken ct = default);// =================MinioOssService==================/// <summary>
/// 刪除文件
/// </summary>
/// <param name="fileId">文件id</param>
/// <param name="ct">取消令牌</param>
/// <returns></returns>
public async Task DeleteAsync(long fileId, CancellationToken ct = default)
{// 查詢文件Files? fileInfo = _dbContext.Files.FirstOrDefault(f => f.IsDeleted == false && f.Id == fileId);if (fileInfo == null){throw new NotFoundException("文件不存在");}var bucket = fileInfo.IsPublic ? _options.Value.PublicBucket : _options.Value.PrivateBucket;await EnsureBucketAsync(bucket, ct);var rmArgs = new RemoveObjectArgs().WithBucket(bucket).WithObject(fileInfo.ObjectName);await _client.RemoveObjectAsync(rmArgs, ct);// 刪除數據庫記錄SettingCommProperty.Delete(fileInfo);await _dbContext.SaveChangesAsync(fileInfo);
}

刪除文件功能是資源管理中的重要組成部分。在IOssService接口中,我們定義了DeleteAsync方法,該方法接收文件ID和取消令牌作為參數。這個異步方法負責安全地刪除存儲在MinIO中的文件以及相關的數據庫記錄。

MinioOssService的具體實現中,DeleteAsync方法首先通過LINQ查詢從數據庫中獲取文件信息。查詢條件確保只查找未刪除的文件(IsDeleted為false)且ID匹配的記錄。如果找不到符合條件的文件記錄,方法會拋出NotFoundException異常,及時通知調用方文件不存在。

獲取到文件信息后,方法會根據文件的IsPublic屬性決定從哪個存儲桶中刪除文件。通過_options.Value訪問配置信息,如果是公開文件則使用PublicBucket,否則使用PrivateBucket。在執行刪除操作前,方法會調用EnsureBucketAsync確保目標存儲桶存在,這是一個額外的安全檢查。接下來,方法使用MinIO客戶端的RemoveObjectAsync方法執行實際的文件刪除操作。RemoveObjectArgs通過鏈式調用配置了必要的參數,包括存儲桶名稱和文件的對象名稱。這個操作會從MinIO存儲中物理刪除文件。最后我們調用_dbContext.SaveChangesAsync(ct);方法將文件標記為已刪除。

2.4 獲取前端直傳憑證

在實際的文件上傳場景中,我們經常需要考慮前端直接上傳文件到對象存儲服務的需求。這種直傳方案可以有效減輕應用服務器的負載,提升上傳性能和用戶體驗。為了實現這個功能,我們需要提供一個預簽名的上傳URL給前端使用。這個預簽名URL本質上是一個臨時的、帶有授權信息的上傳端點,它允許客戶端在限定時間內直接向對象存儲服務發起上傳請求,而無需通過應用服務器中轉文件數據。

預簽名URL的工作機制是通過在URL中嵌入臨時的訪問憑證和必要的參數信息,使得客戶端能夠在有限時間內執行特定的操作(在這里是上傳文件)。當前端獲取到這個預簽名URL后,就可以直接使用標準的HTTP PUT請求將文件上傳到這個地址。這種方式不僅能顯著提升上傳效率,還能減少服務器的帶寬消耗和處理負擔。同時,由于預簽名URL具有時效性和操作限制,也保證了上傳操作的安全性。在大文件上傳或高并發場景下,這種直傳方案的優勢尤為明顯。我們來看一下代碼實現:

// =================IOssService===================/// <summary>
/// 生成用于前端直傳的預簽名 PUT URL
/// </summary>
/// <param name="fileName">對象名稱</param>
/// <param name="isPublic">是否公開桶</param>
/// <param name="ct">取消令牌</param>
Task<PresignedURLResponse> GetPresignedPutUrlAsync(string fileName, bool isPublic, CancellationToken ct = default);// =================MinioOssService==================/// <summary>
/// 生成前端直傳的預簽名 PUT URL
/// </summary>
/// <param name="fileName"></param>
/// <param name="isPublic"></param>
/// <param name="ct"></param>
public async Task<PresignedURLResponse> GetPresignedPutUrlAsync(string fileName, bool isPublic,CancellationToken ct = default)
{// 拼接日期路徑和唯一標識string objectName = $"{DateTime.UtcNow:yyyy/MM/dd}/{Guid.NewGuid():N}{Path.GetExtension(fileName)}";var bucket = isPublic ? _options.Value.PublicBucket : _options.Value.PrivateBucket;await EnsureBucketAsync(bucket, ct);int expirySeconds = _options.Value.UploadTokenExpireSeconds;var preArgs = new PresignedPutObjectArgs().WithBucket(bucket).WithObject(objectName).WithExpiry(expirySeconds);string uploadUrl = await _client.PresignedPutObjectAsync(preArgs);PresignedURLResponse presignedUrlResponse = new PresignedURLResponse(){UploadUrl = uploadUrl,ObjectName = objectName,OriginalFileName = fileName};return presignedUrlResponse;
}

IOssService接口中,我們定義了GetPresignedPutUrlAsync方法,該方法接收文件名、是否公開訪問的標志以及取消令牌作為參數,返回一個包含預簽名上傳URLPresignedURLResponse對象。

MinioOssService的具體實現中,GetPresignedPutUrlAsync方法首先通過組合當前UTC時間的年月日路徑格式和一個GUID,生成一個唯一的對象名稱,并保留原始文件的擴展名。這種命名方式既保證了文件名的唯一性,又實現了按日期的文件組織結構。根據isPublic參數,方法會選擇使用公開桶還是私有桶,并通過EnsureBucketAsync方法確保目標存儲桶存在。

接下來,方法從配置中獲取上傳令牌的過期時間UploadTokenExpireSeconds,并使用MinIO客戶端的PresignedPutObjectArgs創建預簽名請求參數。這些參數包括存儲桶名稱、對象名稱和過期時間。通過調用MinIO客戶端的PresignedPutObjectAsync方法,生成一個帶有認證信息的臨時上傳URL。

最后,方法將生成的上傳URL、對象名稱和原始文件名封裝到PresignedURLResponse對象中返回。這個響應對象包含了客戶端執行直傳所需的所有信息。前端可以使用返回的預簽名URL直接向MinIO發起PUT請求上傳文件,既提高了上傳效率,又減輕了應用服務器的負擔。

2.5 確認文件上傳成功

在前端完成文件上傳到MinIO存儲后,系統需要一個確認機制來驗證上傳是否成功并將文件信息持久化到數據庫中。這個確認過程對于維護文件系統的完整性和一致性至關重要。前端會將文件的關鍵信息,包括對象名稱、文件大小、內容類型、原始文件名等發送到服務端。服務端首先會通過MinIO的API驗證文件是否確實存在于存儲桶中,確保文件上傳的完整性。驗證通過后,服務端會在數據庫中創建相應的文件記錄,建立起文件元數據與實際存儲對象之間的映射關系。這種雙重確認機制不僅能夠及時發現上傳過程中的問題,還能確保系統中的文件記錄始終與實際存儲的文件保持同步,為后續的文件管理和訪問提供可靠的基礎。實現代碼如下:

// =================IOssService===================/// <summary>
/// 文件上傳確認
/// </summary>
/// <param name="request">上傳確認請求</param>
/// <returns></returns>
Task ConfirmUploadAsync(ConfirmUploadRequest request, CancellationToken ct = default);// =================MinioOssService==================/// <summary>
/// 文件上傳確認
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public async Task ConfirmUploadAsync(ConfirmUploadRequest request, CancellationToken ct = default)
{// 驗證文件是否真的存在于 MinIO 中var bucket = request.IsPublic ? _options.Value.PublicBucket : _options.Value.PrivateBucket;try{var statArgs = new StatObjectArgs().WithBucket(bucket).WithObject(request.ObjectName);var objectStat = await _client.StatObjectAsync(statArgs, ct);// 創建文件記錄var fileInfo = new Files{ObjectName = request.ObjectName,IsPublic = request.IsPublic,Size = request.FileSize,ContentType = request.ContentType,OriginalName = request.OriginalFileName};SettingCommProperty.Create(fileInfo);_dbContext.Files.Add(fileInfo);await _dbContext.SaveChangesAsync(ct);}catch (Exception ex){_logger.LogError(ex, "無法確認文件上傳:{ObjectName}", request.ObjectName);throw new BadRequestException($"無法確認文件上傳: {request.ObjectName}");}
}

文件上傳確認功能是確保文件成功上傳到MinIO存儲系統的重要環節。在IOssService接口中,我們定義了ConfirmUploadAsync方法,該方法接收一個ConfirmUploadRequest類型的請求參數和一個可選的取消令牌參數。這個方法的主要職責是驗證文件是否確實存在于MinIO存儲中,并在確認成功后在數據庫中創建相應的文件記錄。

MinioOssService中的具體實現中,首先根據請求中的IsPublic屬性決定使用公共存儲桶還是私有存儲桶。這種設計允許系統靈活處理不同訪問權限的文件存儲需求。接下來,通過創建StatObjectArgs對象并設置相應的存儲桶和對象名稱,使用MinIO客戶端的StatObjectAsync方法來驗證文件的存在性。這個操作會檢查文件的元數據,如果文件不存在或無法訪問,將會拋出異常。

如果文件驗證成功,方法會創建一個新的Files實體對象,用于在數據庫中記錄文件信息。這個實體包含了文件的關鍵屬性:對象名稱、公共訪問標志、文件大小、內容類型以及原始文件名。通過SettingCommProperty.Create方法設置通用屬性后,將文件記錄添加到數據庫上下文中,并通過SaveChangesAsync保存更改。

為了確保系統的健壯性,整個操作被包裝在try-catch塊中。如果在驗證或保存過程中發生任何異常,會記錄詳細的錯誤日志,并拋出一個BadRequestException異常,提供清晰的錯誤信息給調用方。

三、總結

本文詳細介紹了在孢子記賬項目中如何集成和使用MinIO對象存儲服務來管理用戶頭像和賬單圖片等文件資源。文章首先深入講解了MinIO的核心特性、優勢以及在現代云原生應用中的廣泛應用場景,并通過Docker方式演示了MinIO的安裝部署過程。隨后,文章重點展示了如何構建資源微服務,實現了包括文件上傳和URL獲取等在內的核心功能。在實現過程中,不僅考慮了公共訪問和私有訪問兩種場景,還通過預簽名URL機制確保了文件訪問的安全性。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/97789.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/97789.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/97789.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

ESP32三種主流的開發環境

ESP32三種主流的開發環境 1. ESP-IDF (Espressif IoT Development Framework) 這是樂鑫官方提供的專業開發框架&#xff0c;基于FreeRTOS實時操作系統。 特點&#xff1a; 功能最全面&#xff0c;性能最優支持所有ESP32硬件特性使用C/C編程專業級調試工具完整的組件庫和API 適合…

HarmonyOS圖形處理:Canvas繪制與動畫開發實戰

本文將全面介紹HarmonyOS 5中Canvas組件的使用方法和動畫開發技巧&#xff0c;通過詳細的代碼示例和最佳實踐&#xff0c;幫助您掌握圖形繪制和動態效果實現的核心技能。 1. Canvas組件基礎與核心API Canvas是HarmonyOS中用于2D圖形繪制的重要組件&#xff0c;提供了豐富的繪圖…

CCAFusion:用于紅外與可見光圖像融合的跨模態坐標注意力網絡

CCAFusion&#xff1a;用于紅外與可見光圖像融合的跨模態坐標注意力網絡 CCAFusion: Cross-Modal Coordinate Attention Network for Infrared and Visible Image Fusion 摘要 紅外與可見光圖像融合旨在生成一幅包含全面信息的圖像&#xff0c;該圖像既能保留豐富的紋理特征&a…

ESP32-P4小智編譯歷險記:從“編譯失敗“到“成功燒錄“的奇幻之旅,xiaozhi智能聊天機器人編譯避坑心得

?? ESP32-P4:AI小智編譯歷險記:從"編譯失敗"到"成功燒錄"的奇幻之旅 要編譯其他芯片esp32s3-s2-c3,遇到問題也可以在這里交流 “每一個編譯錯誤都是成長的機會,每一次成功都是堅持的勝利!” —— 某位被編譯器折磨的程序員 源碼地址:https://githu…

DIFY 項目中通過 Makefile 調用 Dockerfile 并使用 sudo make build-web 命令構建 web 鏡像的方法和注意事項

DIFY 項目中通過 Makefile 調用 Dockerfile 并使用 sudo make build-web 命令構建 web 鏡像的場景,以下是具體方法和注意事項總結: 一、通過 sudo make build-web 構建 web 鏡像的核心方法 1. 理解 Makefile 與 Dockerfile 的關聯 Makefile 的作用:DIFY 的 Makefile 中定義…

重學前端015 --- 響應式網頁設計 CSS變換

文章目錄skew()transformcursortransition.arm .left {} 和 .arm.left {} 區別skew() skew 傾斜變換函數&#xff0c;該函數有兩個參數。第一個是剪切x軸的角度&#xff0c;第二個是剪切y軸的角度。 transform: skew(0deg, 44deg);transform .arm.left {top: 35%;left: 5%;t…

【GMX v1實戰】時序風險結算與資本成本:深度解析 GMX 永續合約的資金費率機制

在去中心化衍生品交易平臺GMX中&#xff0c;當你準備開立杠桿合約倉位&#xff08;無論是做多還是做空某個資產&#xff09;時&#xff0c;系統會默默執行一個關鍵前置動作——調用名為 ??updateCumulativeFundingRate?? 的函數。這個看似普通的“準備工作”&#xff0c;實…

中宇聯云計算SD-WAN的售后服務怎么樣

前言在數字化轉型浪潮中&#xff0c;企業選擇SD-WAN服務商不僅關注技術性能&#xff0c;更看重售后服務的質量與可靠性。中宇聯云計算作為行業領先者&#xff0c;其SD-WAN售后服務體系已成為行業標桿。隨著全球數字化進程加速&#xff0c;企業對廣域網&#xff08;WAN&#xff…

【Kubernetes】K8s 集群外服務配置 Service 訪問

在 Kubernetes 集群中&#xff0c;內部服務可通過 Service-name 進行訪問。那么&#xff0c;對于集群外的服務&#xff0c;Pod 應該如何通過 Service 進行訪問呢&#xff1f;一起來看看吧&#xff01;此處舉例以 Pod 訪問集群外部的 Mysql 數據庫1、創建 Service# 創建 Service…

Linux 開發工具(1)

從開始講Linux&#xff0c;我們的目標絕不止于寫幾個命令這么簡單。我們的目的是在Linux系統上做開發。因此學習Linux的開發工具也是必不可少的。本章將重點講解&#xff1a;包管理器apt(CentOS叫yum&#xff0c;這里用ubuntu舉例)&#xff0c;vim編輯器。一.包管理器apt1.安裝…

redis面試點記錄

1、主從復制psync-> runid->runid是&#xff1f;則是全量->返回fullresync和runid和復制進度->bgsave命令準備RDB文件->之后的命令寫入replication_buffer->發送RDB->發送replication_buffer的信息repl_backlog_buffer環型緩沖區&#xff0c;主節點只有一…

Elastic APM 與 Elasticsearch 集成:構建完整可觀測性棧

引言 Elastic APM 深度依賴 Elasticsearch 作為數據后端&#xff0c;但正確集成可以解鎖更強大的功能&#xff0c;如自定義查詢、聚合分析和與其它 Elastic 工具的協同。本文探討 APM 與 Elasticsearch 的集成細節&#xff0c;包括數據流、索引管理以及高級用例&#xff0c;幫助…

開源模型應用落地-基于DPO的Qwen3-4B意圖理解精準對齊實踐(二十)

一、前言 在大模型技術蓬勃發展的今天,如何讓AI真正“理解”用戶意圖,而非僅僅生成流暢文本,已成為落地應用的核心瓶頸。尤其是在客服、搜索、智能助手等場景中,模型對用戶query的深層語義解析能力,直接決定了交互體驗的成敗。然而,經過標準SFT(監督微調)訓練的模型,往…

23種設計模式案例

一、創建型模式 1. 單例模式 (Singleton Pattern) 應用場景: 全局狀態管理、全局配置、共享資源訪問 // 全局狀態管理器 class Store {constructor() {if (Store.instance) return Store.instance;this.state {};Store.instance this;}getState(key) { return this.state[key…

ctfshow_web13-----------文件上傳.user.ini

打開題目發現是一個文件上傳題掃描后發現存在upload.php.bak.bak是備份文件拿到源碼正則過濾了php&#xff0c;文件大小<24,文件名小于9經嘗試&#xff0c;改后綴php5,ptml均不行&#xff0c;使用.htaccess文件也不成功上傳上傳.user.ini&#xff0c;在文件中寫上auto_prepe…

圖像拼接案例,摳圖案例

目錄 一.圖像拼接案例 1.圖像拼接項目介紹 2.核心步驟 ①計算圖片特征點及描述符 ②匹配特征點&#xff0c;使用暴力匹配器 ③篩選有效匹配 ④計算透視變換矩陣 ⑤應用變換和拼接 二.摳圖案例 1.縮放旋轉處理 2.轉化為灰度圖并二值化 3.找出所有輪廓&#xff0c;并在…

【左程云算法筆記016】雙端隊列-雙鏈表和固定數組實現

目錄 1&#xff09;雙端隊列的介紹 2&#xff09;雙端隊列用雙鏈表的實現代碼演示 3&#xff09;雙端隊列用固定數組的實現 代碼演示 視頻 【算法講解016【入門】雙端隊列-雙鏈表和固定數組實現】 Leecode leecode641 設計循環雙端隊列 1&#xff09;雙端隊列的介紹 可以…

ffplay視頻輸出和尺寸變換

視頻輸出模塊 視頻輸出初始化的主要流程 我們開始分析視頻&#xff08;圖像&#xff09;的顯示。 因為使?了SDL&#xff0c;?video的顯示也依賴SDL的窗?顯示系統&#xff0c;所以先從main函數的SDL初始化看起&#xff08;節選&#xff09;&#xff1a; int main(int argc, c…

協議_https協議

http http協議是將數據以明文的形式在網絡上傳輸。若是傳輸的數據中包含一些敏感信息比如銀行卡信息等可能會被有心人攻擊造成信息泄露或被篡改。 總結&#xff1a;http協議進行數據傳輸難以保證數據的隱私性以及數據完整性&#xff0c;為了保證數據的準確定引入了https這一協…

阿里云 騰訊云 API 自動化查詢指南

文章目錄一、核心思路與架構建議二、經驗與核心建議三、技術方案選型建議四、API使用詳解4.1 阿里云4.2 騰訊云五、進階&#xff1a;與內部系統聯動免費個人運維知識庫&#xff0c;歡迎您的訂閱&#xff1a;literator_ray.flowus.cn 一、核心思路與架構建議 自動化流程可以概括…