文章目錄
- 項目地址
- 一、Http Cache
- 1.1 服務注冊
- 1.2 Validation with ETag
- 1. 添加ETagMiddleware中間件
- 2. 創建內存ETag存儲器
- 3. 服務注冊
- 4. 測試
- 二、使用ETag實現樂觀鎖
- 2.1 添加樂觀鎖方法
- 2.2 修改Controller
- 2.3 測試樂觀鎖
- 三、Rate Limiting
- 3.1 添加速率控制服務
- 1. 在Program里添加服務
- 2. 添加AddRateLimiting方法
- 3. 使用
- 4. 弊端
- 四、Idempotent Request 冪等請求
- 4.1 創建IdempotentRequestAttribute特性
- 4.2 使用
- 4.3 測試
- 五、異步大文件上傳
- 5.1 創建Entities
- 1. 修改之前的Entry實體
- 2. EntryImportJob
- 5.2 創建DTOs
- 5.3 創建ProcessEntryImportJob
項目地址
- 教程作者:
- 教程地址:
- 代碼倉庫地址:
- 所用到的框架和插件:
dbt
airflow
一、Http Cache
緩存的類型:
1. Client cache
2. Gateway cache(reverse poxy)
3. Proxy cache(CDN)
1.1 服務注冊
- 注冊cache服務
- 注冊中間件
1.2 Validation with ETag
- 使用Rest api 返回ETag頭部進行緩存
流程:- 第一次請求,沒有緩存,數據庫查詢后,返回并且添加ETag響應頭
- 響應頭存儲在瀏覽器緩存,如果還是相同的請求或者沒有更改,則返回304not modified,并從內存緩存里拿數據
- 如果更改或者沒有緩存數據,則返回新的ETag
1. 添加ETagMiddleware中間件
- 用于生成Etag頭部和判斷響應
namespace DevHabit.Api.Middleware;//定義一個中間件,表示請求管道中的下一個中間件
public sealed class ETagMiddleware(RequestDelegate next)
{public async Task InvokeAsync(HttpContext context, InMemoryETagStore eTagStore){//1.如果當前請求方法是 POST、PUT、PATCH 或 DELETE,就跳過 ETag 邏輯if (CanSkipETag(context)){await next(context);return;}//2.獲取當前請求的 URI,用作標識資源的 key,稍后要用來生成和比對 ETag。string resourceUri = context.Request.Path.Value!;//3.從請求頭中讀取客戶端帶來的 If-None-Match ETag,用于判斷資源是否修改過。去掉引號是為了統一格式。string? ifNoneMatch = context.Request.Headers.IfNoneMatch.FirstOrDefault()?.Replace("\"", "");//4.如果請求方法是 GET 或 HEAD,就從 ETag 存儲中獲取當前資源的 ETagStream originalStream = context.Response.Body; //獲取原始響應流using var memoryStream = new MemoryStream(); //創建一個內存流,用于緩存響應內容context.Response.Body = memoryStream; //將響應流寫入內存流,以便后續讀取響應內容//5.執行請求管道中的下一個中間件(或控制器),并把響應寫入 memoryStream 中gawait next(context); //6. 如果響應狀態碼是 200 OK,并且響應內容類型是 JSON,就計算 ETagif (IsETaggableResponse(context)){memoryStream.Position = 0; //將內存流位置重置到開頭byte[] responseBody = await GetResponseBody(memoryStream); //讀取內存流中的響應內容string eTag = GenerateETag(responseBody); //計算 ETageTagStore.SetETag(resourceUri, eTag); //將 ETag 存儲到 ETag 存儲中context.Response.Headers.ETag = $"\"{eTag}\""; //將 ETag 添加到響應頭中context.Response.Body = originalStream; //將響應流恢復為原始響應流//9. 如果 ETag 存儲中已經有當前資源的 ETag,并且和計算出來的 ETag 一致,就返回 304 Not Modifiedif (context.Request.Method == HttpMethods.Get && ifNoneMatch == eTag) {context.Response.StatusCode = StatusCodes.Status304NotModified;context.Response.ContentLength = 0;return;}}//如果內容有更新,復制緩沖的響應內容到原始響應流中,讓客戶端收到響應。memoryStream.Position = 0;await memoryStream.CopyToAsync(originalStream);}//判斷當前響應是否適合使用 ETag 進行緩存處理private static bool IsETaggableResponse(HttpContext context){return context.Response.StatusCode == StatusCodes.Status200OK &&(context.Response.Headers.ContentType.FirstOrDefault()?.Contains("json", StringComparison.OrdinalIgnoreCase) ?? false);}//讀取 MemoryStream 中的響應內容,并以 byte[] 的形式返回private static async Task<byte[]> GetResponseBody(MemoryStream memoryStream){using var reader = new StreamReader(memoryStream, leaveOpen: true);memoryStream.Position = 0;string content = await reader.ReadToEndAsync();return Encoding.UTF8.GetBytes(content);}//根據響應內容生成 ETag 值private static string GenerateETag(byte[] content){byte[] hash = SHA512.HashData(content);return Convert.ToBase64String(hash);}//判斷當前請求方法是否可以跳過 ETag 邏輯private static bool CanSkipETag(HttpContext context){return context.Request.Method == HttpMethods.Post ||context.Request.Method