一、背景 代碼實例:https://gitee.com/D_C_L/CurtainEtcAOP.git我們實際系統中有很多操作,是不管做多少次,都應該產生一樣的效果或返回一樣的結果。 例如: 1. 前端重覆提交選中的數據,應該後臺只產生對應這個數據的一個反應結果。 2. 我們發起一筆付款請求,應該只 ...
一、背景
代碼實例:https://gitee.com/D_C_L/CurtainEtcAOP.git
我們實際系統中有很多操作,是不管做多少次,都應該產生一樣的效果或返回一樣的結果。
例如:
1. 前端重覆提交選中的數據,應該後臺只產生對應這個數據的一個反應結果。
2. 我們發起一筆付款請求,應該只扣用戶賬戶一次錢,當遇到網路重發或系統bug重發,也應該只扣一次錢;
3. 發送消息,也應該只發一次,同樣的簡訊發給用戶,用戶會哭的;
4. 創建業務訂單,一次業務請求只能創建一個,創建多個就會出大問題。
等等很多重要的情況,這些邏輯都需要冪等的特性來支持。
二、冪等性概念
冪等(idempotent、idempotence)是一個數學與電腦學概念,常見於抽象代數中。
在編程中.一個冪等操作的特點是其任意多次執行所產生的影響均與一次執行的影響相同。冪等函數,或冪等方法,是指可以使用相同參數重覆執行,並能獲得相同結果的函數。這些函數不會影響系統狀態,也不用擔心重覆執行會對系統造成改變。例如,“getUsername()和setTrue()”函數就是一個冪等函數.
更複雜的操作冪等保證是利用唯一交易號(流水號)實現.
我的理解:冪等就是一個操作,不論執行多少次,產生的效果和返回的結果都是一樣的
一,正式開始了
現在我們們要做一個點擊按鈕之後等待後臺返回之後才可以再次請求方法,其他的重覆請求直接進行攔截
在這裡我使用攔截器進行實現(也可以使用中間件來實現),主要就是以AOP(切麵編程是面向對象的優化),將一些緊密的業務進行切開,在中間進行自己的一些邏輯處理
主要的實現思路就是使用toekn+Redis緩存進行(當我們訪問某個方法的時候給用戶的客戶端存取一個cookie)cookie裡面主要是存的toekn,當我們訪問介面的時候一定要帶上有效的
token才能訪問方法或者控制器。
客戶端訪問方法的時候存token到Redis裡面,當請求方法的時候帶上token,攔截器裡面判斷是不是存放在Redis裡面的token如果不是的直接攔截返回,如果帶上的是有效的token刪除舊token
生成一個新的token存放在cookie裡面,存到Redis,給第二次請求發放token。
代碼理解:
//視圖頁面 public partial class HomeController : Controller { private readonly RedisHelp cache = new RedisHelp(); /// <summary> /// 首頁 獲取token /// </summary> /// <returns></returns> [NoSign] public IActionResult Index() { string token = Guid.NewGuid().ToString(); HttpContext.Response.Cookies.Append("token", token); cache.SetValue("token", token); return View(); } }
當瀏覽器訪問這個視圖的時候給他發放一個token。
這裡存在cooke的好處就是,下一次請求會直接帶上上一次發放的Toekn,我們在前臺就不需要去做任何操作了,這樣攔截器才能算上是一個獨立的模塊。
我們需要訪問這個介面,保證同時點擊的時候只能有一次
/// <summary> /// 介面控制器 /// </summary> public partial class HomeController : Controller { /// <summary> /// 接受提交請求 /// </summary> /// <returns></returns> public JsonResult Submit() { ResponseJson responseJson = new ResponseJson(); responseJson.msg = "更新了token"; return Json(responseJson); } }
下麵就是攔截器的代碼
/// <summary> /// 適合全局的 /// </summary> public class SignFilter : ActionFilterAttribute { private RedisHelp cache = new RedisHelp(); /// <summary> /// 請求之前 /// </summary> /// <param name="context"></param> public override void OnActionExecuting(ActionExecutingContext context) { // 判斷是否檢查登陸 var noNeedCheck = false; if (context.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor) { noNeedCheck = controllerActionDescriptor.MethodInfo.GetCustomAttributes(inherit: true) .Any(a => a.GetType().Equals(typeof(NoSignAttribute))); } if (noNeedCheck)return; ResponseJson responseJson = new ResponseJson(); var token = context.HttpContext.Request.Cookies["token"]; #region 判斷數據有效性 if (string.IsNullOrWhiteSpace(token)) { responseJson.msg = "toekn不能空"; context.Result = new JsonResult(responseJson); return; } else if (cache.GetValue("token") ==null) { responseJson.msg = "toekn不能空"; context.Result = new JsonResult(responseJson); return; } else if (!cache.DeleteKey("token")) { responseJson.msg = "token已不存在"; context.Result = new JsonResult(responseJson); return; } #endregion base.OnActionExecuting(context); } /// <summary> /// 請求過了之後,在去分發token /// </summary> /// <param name="context"></param> public override void OnActionExecuted(ActionExecutedContext context) { //隨機值 string redisToken = Guid.NewGuid().ToString() + new Random().Next(100000, 99999999); context.HttpContext.Response.Cookies.Append("token", redisToken); //初使化並設置Cookie的名稱 cache.SetValue("token", redisToken); } } /// <summary> /// 不需要登陸的地方加個特性 /// </summary> public class NoSignAttribute : ActionFilterAttribute { }
但是這個只適合於個人在點擊的時候的,下次我分享一下多人的模式。