• ASP.NET Core Web API 幂等性


    API的幂等性(Idempotent),是指调用某个方法1次或N次对资源产生的影响结果都是相同的。
    GET请求默认是幂等的,因为它只是查询资源,而不会修改资源。
    而POST请求默认是不幂等的,多次调用POST方法可能会产生不同的结果,并会创建多个资源。
    想象一下,你在扫码支付时,输入金额后点击了2次“确定”按钮,肯定不希望扣2次款。
    幂等性保证了操作只会执行一次。

    1、思路
    使用ASP.NET Core过滤器来处理POST请求,检查请求头【Headers】中的幂等键(IdempotencyKey)。
    如果在缓存中未检查到IdempotencyKey,则真实执行操作并缓存响应数据,否则直接返回缓存的响应数据。
    这样,操作只能对资源产生一次影响。

    2、IdempotentAttributeFilter
    创建自定义Filter,使用OnActionExecuting方法在执行操作前检查缓存,如有缓存直接返回context.Result;使用OnResultExecuted方法在执行操作后缓存响应。

    1. using Microsoft.AspNetCore.Http;
    2. using Microsoft.AspNetCore.Mvc;
    3. using Microsoft.AspNetCore.Mvc.Filters;
    4. using Microsoft.Extensions.Caching.Distributed;
    5. using Newtonsoft.Json;
    6. using System;
    7. using System.Collections.Generic;
    8. using System.IO;
    9. using System.Linq;
    10. using System.Threading.Tasks;
    11. namespace WebApi
    12. {
    13. ///
    14. ///
    15. ///
    16. public class IdempotentAttributeFilter : IActionFilter, IResultFilter
    17. {
    18. private readonly IDistributedCache _distributedCache;
    19. private bool _isIdempotencyCache = false;
    20. const string IdempotencyKeyHeaderName = "IdempotencyKey";
    21. private string _idempotencyKey;
    22. ///
    23. ///
    24. ///
    25. ///
    26. public IdempotentAttributeFilter(IDistributedCache distributedCache)
    27. {
    28. _distributedCache = distributedCache;
    29. }
    30. ///
    31. ///
    32. ///
    33. ///
    34. public void OnActionExecuting(ActionExecutingContext context)
    35. {
    36. Microsoft.Extensions.Primitives.StringValues idempotencyKeys;
    37. context.HttpContext.Request.Headers.TryGetValue(IdempotencyKeyHeaderName, out idempotencyKeys);
    38. _idempotencyKey = idempotencyKeys.ToString();
    39. var cacheData = _distributedCache.GetString(GetDistributedCacheKey());
    40. if (cacheData != null)
    41. {
    42. context.Result = JsonConvert.DeserializeObject(cacheData);
    43. _isIdempotencyCache = true;
    44. return;
    45. }
    46. }
    47. ///
    48. ///
    49. ///
    50. ///
    51. public void OnResultExecuted(ResultExecutedContext context)
    52. {
    53. //已缓存
    54. if (_isIdempotencyCache)
    55. {
    56. return;
    57. }
    58. var contextResult = context.Result;
    59. DistributedCacheEntryOptions cacheOptions = new DistributedCacheEntryOptions();
    60. //相对过期时间
    61. //cacheOptions.SlidingExpiration = TimeSpan.FromSeconds(10);
    62. //绝对过期时间
    63. cacheOptions.AbsoluteExpirationRelativeToNow = new TimeSpan(24, 0, 0);
    64. //缓存:
    65. _distributedCache.SetString(GetDistributedCacheKey(), JsonConvert.SerializeObject(contextResult), cacheOptions);
    66. }
    67. ///
    68. ///
    69. ///
    70. ///
    71. public void OnActionExecuted(ActionExecutedContext context)
    72. {
    73. }
    74. ///
    75. ///
    76. ///
    77. ///
    78. public void OnResultExecuting(ResultExecutingContext context)
    79. {
    80. }
    81. private string GetDistributedCacheKey()
    82. {
    83. return "Idempotency:" + _idempotencyKey;
    84. }
    85. }
    86. }

    3、创建自定义Attribute
    声明了IdempotentAttribute的Class或者Method,在运行时会创建IdempotentAttributeFilter。

    1. using Microsoft.AspNetCore.Http;
    2. using Microsoft.AspNetCore.Mvc;
    3. using Microsoft.AspNetCore.Mvc.Filters;
    4. using Microsoft.Extensions.Caching.Distributed;
    5. using Newtonsoft.Json;
    6. using System;
    7. using System.Collections.Generic;
    8. using System.IO;
    9. using System.Linq;
    10. using System.Threading.Tasks;
    11. namespace WebApi
    12. {
    13. ///
    14. ///
    15. ///
    16. [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
    17. public class IdempotentAttribute : Attribute, IFilterFactory
    18. {
    19. ///
    20. ///
    21. ///
    22. public bool IsReusable => false;
    23. ///
    24. ///
    25. ///
    26. ///
    27. ///
    28. public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
    29. {
    30. var distributedCache = (IDistributedCache)serviceProvider.GetService(typeof(IDistributedCache));
    31. var filter = new IdempotentAttributeFilter(distributedCache);
    32. return filter;
    33. }
    34. }
    35. }

    4、新建ASP.NET Core Web API项目
    创建 WeatherForecastController 控制器,为Post方法加上【Idempotent】
    这里用一个静态变量模拟数据库,POST请求写入数据,GET请求读取数据

    1. using Microsoft.AspNetCore.Mvc;
    2. using Microsoft.Extensions.Logging;
    3. using System;
    4. using System.Collections.Generic;
    5. using System.Linq;
    6. using System.Net;
    7. using System.Threading.Tasks;
    8. namespace WebApi.Controllers
    9. {
    10. ///
    11. ///
    12. ///
    13. [Route("api/[controller]")]
    14. [ApiController]
    15. public class WeatherForecastController : ControllerBase
    16. {
    17. private static List _db = new List();
    18. private static readonly string[] Summaries = new[]
    19. {
    20. "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    21. };
    22. ///
    23. ///
    24. ///
    25. public WeatherForecastController()
    26. {
    27. }
    28. ///
    29. ///
    30. ///
    31. ///
    32. ///
    33. [Idempotent]
    34. [HttpPost]
    35. public WeatherForecast Post(int temperature)
    36. {
    37. var data = new WeatherForecast { TemperatureC = temperature };
    38. _db.Add(data);
    39. return data;
    40. }
    41. ///
    42. ///
    43. ///
    44. ///
    45. [HttpGet()]
    46. public IEnumerable Get()
    47. {
    48. var rng = new Random();
    49. return _db.Select(p => new WeatherForecast
    50. {
    51. TemperatureC = p.TemperatureC,
    52. Summary = Summaries[rng.Next(Summaries.Length)]
    53. })
    54. .ToArray();
    55. }
    56. }
    57. public class WeatherForecast
    58. {
    59. public DateTime Date { get; set; }
    60. public int TemperatureC { get; set; }
    61. public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
    62. public string Summary { get; set; }
    63. }
    64. }

    5、注册分布式缓存
    必须增加分布式缓存,用于保存幂等键的值和响应数据。
    管理 NuGet 程序包(N)...

    1. Microsoft.Extensions.Caching.SqlServer
    2. Microsoft.Extensions.Caching.Redis
    3. Microsoft.Extensions.Caching.StackExchangeRedis

    Startup.cs

    1. public void ConfigureServices(IServiceCollection services)
    2. {
    3. //分布式 SQL Server 缓存
    4. services.AddDistributedSqlServerCache(opt =>
    5. {
    6. opt.ConnectionString = Configuration.GetConnectionString("DefaultConnection");
    7. opt.SchemaName = "dbo";
    8. opt.TableName = "sys_distributed_cache";
    9. opt.DefaultSlidingExpiration = TimeSpan.FromMinutes(10);
    10. opt.ExpiredItemsDeletionInterval = TimeSpan.FromMinutes(5);
    11. });
    12. //分布式 Redis 缓存
    13. services.AddDistributedRedisCache(cfg =>
    14. {
    15. cfg.Configuration = Configuration.GetConnectionString("RedisConnection");
    16. });
    17. //分布式 StackExchangeRedis 缓存
    18. services.AddStackExchangeRedisCache(options =>
    19. {
    20. options.Configuration = "localhost";
    21. options.InstanceName = "SampleInstance";
    22. });
    23. //分布式缓存
    24. services.AddDistributedMemoryCache();
    25. }

    在数据库中新建一个名叫“CacheDB”的数据库,然后以管理员身份cmd运行下面指令,会创建一张名叫“CacheTable”表,相应的缓存信息都存在于这张表中。

    1. dotnet sql-cache create <connection string> <schema> <table>
    2. dotnet sql-cache create "Server=localhost;User=sa;Password=000000;Database=CacheDB" dbo CacheTable

    成功后会提示【Table and index were created successfully】
    表结构

    1. CREATE TABLE [dbo].[CacheTable](
    2. [Id] [nvarchar](449) NOT NULL,
    3. [Value] [varbinary](max) NOT NULL,
    4. [ExpiresAtTime] [datetimeoffset](7) NOT NULL,
    5. [SlidingExpirationInSeconds] [bigint] NULL,
    6. [AbsoluteExpiration] [datetimeoffset](7) NULL,
    7. CONSTRAINT [pk_Id] PRIMARY KEY CLUSTERED
    8. (
    9. [Id] ASC
    10. )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
    11. ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
    12. CREATE NONCLUSTERED INDEX [Index_ExpiresAtTime] ON [dbo].[CacheTable]
    13. (
    14. [ExpiresAtTime] ASC
    15. )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]

    6、测试
    运行Web API,使用不同IdempotencyKey执行POST请求,然后获取数据

    code

    1. POST /api/WeatherForecast?temperature=1000 HTTP/1.1
    2. Host: localhost:8001
    3. IdempotencyKey: 1000

    *
    *

  • 相关阅读:
    Springboot求职招聘系统毕业设计源码250911
    flink 状态
    忧虑随日落一起消失&做事的三重门
    复现一个循环以及俩个循环问题
    前端面试0906
    3-2 中阶API示范
    【PCB专题】如何在嘉立创8月1日起的新规则下免费打样
    项目管理中最常见的问题有哪些?
    Cholesterol艾美捷胆固醇基参方案
    部署k8s集群
  • 原文地址:https://blog.csdn.net/KingCruel/article/details/126014937