圖片上傳是很常見的功能,裡面有些固定的操作也可以沉澱下來。
本文記錄使用Abp vNext做圖片上傳的姿勢。
本文的技術核心與Abp無關,Abp只是手段!
目標
- 上傳圖片----->預覽圖片----->確定儲存
- 支援叢集部署
實現思路:
① 上傳圖片要使用WebAPI特定媒體型別:multipart/form-data
;
② 因為要做圖片預覽,故在上傳時利用AbpCache
做一個臨時快取,返回圖片Id
;
③ 前端利用FileReader
渲染預覽圖;
④ [確定]: 發起持久化WebAPI(利用第②步返回的圖片Id)
為什麼強調支援叢集部署?
就這個功能而言,[上傳預覽]和[確定儲存]是兩次Http WebAPI請求。
如果服務端使用的是Redis等程式外快取: 那這正好是一個Stateless應用功能,叢集環境次功能無懼!
如果服務端使用的是程式內快取:在叢集環境,前後兩次請求有可能打到不同的App服務,後置的[確定儲存]WebAPI因此可能報錯, 此處需要做 [會話親和性] Session affinity
實踐
利用Abp做圖片上傳
IFormFile能力如下紅框:
下面將圖片二進位制流轉化為 base64字串,注入Abp快取元件IDistributedCache<string>
;
快取圖片字串1小時。
[上傳預覽], [確定儲存]的API完整程式碼如下:
/// <summary>
/// 上傳預覽, 返回待上傳的圖片id,Content-Type:multipart/form-data
/// </summary>
/// <returns></returns>
[Consumes("multipart/form-data")]
[Route("upload/preview")]
[ProducesResponseType(typeof(Guid),200)]
[HttpPost]
public async Task<Guid> UploadPicPreviewAsync(IFormFile uploadedFile)
{
var formFileName = uploadedFile.FileName;
if (!new[] { ".png", ".jpg", ".bmp" }.Any((item) => formFileName.EndsWith(item)))
{
throw new AbpValidationException("您上傳的檔案格式必須為png、jpg、bmp中的一種");
}
byte[] bytes;
using (var bodyStream = uploadedFile.OpenReadStream())
{
using (var m = new MemoryStream())
{
await bodyStream.CopyToAsync(m);
bytes = m.ToArray();
}
}
string base64 = Convert.ToBase64String(bytes);
var bgId = Guid.NewGuid();
_cache.Set($"{CurrentUser.TenantId}:bg:{bgId}", base64, new DistributedCacheEntryOptions { SlidingExpiration = new TimeSpan(1, 0, 0) });
return bgId;
}
/// <summary>
/// 儲存圖片,要使用到前置API的預覽圖片id
/// </summary>
/// <param name="createPictureInput"></param>
/// <returns></returns>
[Route("upload/")]
[HttpPost]
public async Task<bool> UploadPicAsync([FromBody] CreatePictureInput createPictureInput)
{
var based64 = await _cache.GetAsync($"{CurrentUser.TenantId}:bg:{createPictureInput.PreviewPicId}");
if (string.IsNullOrEmpty(based64))
throw new AbpException("Cache Hotmap Picture do not find");
var model = ObjectMapper.Map<CreatePictureInput, Picture>(createPictureInput);
model.ProfileId = CurrentUser.TenantId;
model.BlobStorage = Convert.FromBase64String(based64);
return await _pictures.InsertAsync(model)!= null;
}
Default implementation of the IDistributedCache interface is the MemoryDistributedCache which works in-memory.
The Distributed Memory Cache (AddDistributedMemoryCache) is a framework-provided implementation of IDistributedCache that stores items in memory. The Distributed Memory Cache isn't an actual distributed cache. Cached items are stored by the app instance on the server where the app is running.
以上兩段文字來自 Abp和Asp.NETCore官方文件:
- Abp預設的IDistributedCache實現是分散式記憶體快取;
- ASP.NETCore 分散式記憶體快取是框架內建的,是一個假的分散式快取,實際是單純的記憶體快取。
在沒有使用真實分散式快取的情況下, 需要對前後兩個API配置會話親和性。
會話親和性
下面從nginx、Azure、k8s ingress 三角度配置[會話親和性],(全站生效)
會話親和性的實現原理,是在接受客戶端首次請求時響應某個cookie,伺服器會認定使用同一個cookie的請求為一個會話。
1. nginx
屬於nginx負載均衡的範疇:https://docs.nginx.com/nginx/admin-guide/load-balancer/http-load-balancer/
示例如下:
upstream backend {
server backend1.example.com;
server backend2.example.com;
sticky cookie srv_id expires=1h domain=.example.com path=/;
}
2. Azure App Service
Azure pp Service是Azure雲平臺提供的App託管服務,具備多例項自動縮放的能力,
其有關會話親和性的配置如圖:
3. K8S nginx-ingress
註解nginx.ingress.kubernetes.io/affinity
在入口的所有上游中啟用和設定親和性型別。
這樣,請求將總是被定向到相同的上游伺服器。
https://kubernetes.github.io/ingress-nginx/examples/affinity/cookie/
That's All
本文以常見的圖片上傳功能為例,實戰演練了Abp的快取和持久化能力;
引申出對有狀態應用配置會話親和性,部署方式要結合業務功能。
希望對大家有所幫助!