ASP.NET Core Filter通过缓存实现接口防重

接口防重复调用

缓存使用文档请看这篇博客
ASP.NET Core Filter文档请看这篇博客

添加Nuget包添加缓存

--Memory
Install-Package  Microsoft.Extensions.Caching.Memory

--Redis
Install-Package  Microsoft.Extensions.Caching.StackExchangeRedis

Program.cs添加配置


Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

var builder = WebApplication.CreateBuilder(args);

//Memory
builder.Services.AddDistributedMemoryCache();

//redis
builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = "192.168.1.5:6379,password=123456,ssl=False,abortConnect=False";
    options.InstanceName = "App-Instance-Keys";
});

去重RequestRestrictionsAttribute代码

注意这里没有区分Url QueryString和Request Header逻辑

using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Caching.Distributed;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;

namespace WebApplication1
{
    /// <summary>
    /// API限重Filter
    /// </summary>
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
    public sealed class RequestRestrictionsAttribute : ActionFilterAttribute
    {
        /// <summary>
        /// 单次请求最小锁定时间(毫秒)
        /// </summary>
        private readonly int _requestLocktime;

        /// <summary>
        /// 请求控制是否包含Body参数
        /// </summary>
        private readonly bool _includeArguments;

        /// <summary>
        /// Message
        /// </summary>
        private readonly string _message;

        /// <summary>
        /// 缓存Key
        /// </summary>
        private static readonly string KEY_PRIFIX_RequestLock = "myapp:apilock:path:{0}:arguments:{1}";

        /// <summary>
        /// 请求限制
        /// </summary>
        /// <param name="requestLocktime">锁定时间(毫秒),默认五秒</param>
        /// <param name="includeArguments">请求控制是否包含Body参数</param>
        /// <param name="message">错误提示</param>
        public RequestRestrictionsAttribute(int requestLocktime = 5000, bool includeArguments = true, string message = "频繁请求, 请稍后重试")
        {
            _requestLocktime = requestLocktime;
            _includeArguments = includeArguments;
            _message = message;
        }

        public override void OnActionExecuting(ActionExecutingContext context)
        {
            if (context == null)
            {
                return;
            }
            var storage = context.HttpContext.RequestServices.GetService<IDistributedCache>();
            if (storage == null)
            {
                base.OnActionExecuting(context);
                return;
            }
            string requestPath = context.HttpContext.Request.Path;
            //string requestPathAndQuery = context.HttpContext.Request.Path + context.HttpContext.Request.QueryString;
            string actionArguments = _includeArguments ? JsonSerializer.Serialize(context.ActionArguments) : "arguments";

            //*****此处key最好加上context.HttpContext.Request.QueryString和请求头用户信息进行判断*****
            string RequestLockCacheKey = string.Format(KEY_PRIFIX_RequestLock, requestPath, HashEncrypt(actionArguments));
            if (storage.GetString(RequestLockCacheKey) != null) throw new Exception(_message);
            storage.SetString(RequestLockCacheKey, "1", new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMilliseconds(_requestLocktime) });

            //转入用户Action操作
            base.OnActionExecuting(context);
        }

        private static string HashEncrypt(string str)
        {
            byte[] hashedDataBytes = MD5.HashData(Encoding.GetEncoding("gb2312").GetBytes(str));
            //.net 5+
            return Convert.ToHexString(hashedDataBytes);

            //.net 5以下版本
            //StringBuilder sb = new();
            //foreach (byte i in hashedDataBytes) sb.Append(i.ToString("x2"));
            //return sb.ToString();


            //其他加密方式
            //byte[] hashedDataBytes = Encoding.GetEncoding("gb2312").GetBytes(str);
            //byte[] md5hashed32 = MD5.HashData(hashedDataBytes);
            //byte[] hashed40 = SHA1.HashData(hashedDataBytes);
            //byte[] hashed64 = SHA256.HashData(hashedDataBytes);
            //byte[] hashed128 = SHA512.HashData(hashedDataBytes);
        }
    
        /// <summary>
        /// 用MD5加密字符串,可选择生成16位或者32位的加密字符串(.net 4)
        /// </summary>
        /// <param name="password">待加密的字符串</param>
        /// <returns>返回的加密后的字符串</returns>
        private string MD5Encrypt32(string str)
        {
            HashAlgorithm md5Hasher = new SHA1Managed();
            byte[] hashedDataBytes;
            hashedDataBytes = md5Hasher.ComputeHash(Encoding.GetEncoding("gb2312").GetBytes(str));
            StringBuilder tmp = new StringBuilder();
            foreach (byte i in hashedDataBytes)
            {
                tmp.Append(i.ToString("x2"));
            }
            return tmp.ToString();//默认情况
        }
    }
}

Controller中使用

using Microsoft.AspNetCore.Mvc;

namespace WebApplication1.Controllers;

[ApiController]
[Route("[controller]")]
[RequestRestrictions(1000 * 5)]
public class WeatherForecastController : ControllerBase { } 

在方法中使用

[HttpGet]
[RequestRestrictions(1000 * 5)]
public IEnumerable<int> Get()
{
return Enumerable.Range(1, 5);
}

.net framework 4.5 接口防重复调用

using InsCES3.Business.Inspecting;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;

namespace iDam2.DataCollection.Inspecting.Attributes
{
    /// <summary>
    /// API限重Filter
    /// </summary>
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
    public sealed class RequestRestrictionsAttribute : ActionFilterAttribute
    {
        /// <summary>
        /// 单次请求最小锁定时间(毫秒)
        /// </summary>
        private readonly int _requestLocktime;

        /// <summary>
        /// 请求控制是否包含Body参数
        /// </summary>
        private readonly bool _includeArguments;

        /// <summary>
        /// Message
        /// </summary>
        private readonly string _message;

        /// <summary>
        /// 缓存Key
        /// </summary>
        private static readonly string KEY_PRIFIX_RequestLock = "app:apilock:path:{0}:arguments:{1}";

        private CacheBus cacheBus = CacheBus.Instance;

        /// <summary>
        /// 请求限制
        /// </summary>
        /// <param name="requestLocktime">锁定时间(毫秒),默认三秒</param>
        /// <param name="includeArguments">请求控制是否包含Body参数</param>
        /// <param name="message">错误提示</param>
        public RequestRestrictionsAttribute(int requestLocktime = 3000, bool includeArguments = true, string message = "频繁请求, 请稍后重试")
        {
            _requestLocktime = requestLocktime;
            _includeArguments = includeArguments;
            _message = message;
        }

        public override void OnActionExecuting(HttpActionContext context)
        {
            if (context == null)
            {
                return;
            }
            //string requestPath = context.Request.RequestUri.AbsolutePath;
            string requestPathAndQuery = context.Request.RequestUri.PathAndQuery;
            string actionArguments = GetArguments(context);

            //*****此处key最好加上context.HttpContext.Request.QueryString和请求头用户信息进行判断*****
            string RequestLockCacheKey = string.Format(KEY_PRIFIX_RequestLock, requestPathAndQuery, MD5Encrypt32(actionArguments));
            if (cacheBus.Exists<string>(RequestLockCacheKey)) throw new Exception(_message);
            cacheBus.SetCache(RequestLockCacheKey, "1", _requestLocktime);

            //转入用户Action操作
            base.OnActionExecuting(context);
        }

        /// <summary>
        /// 用MD5加密字符串,可选择生成16位或者32位的加密字符串
        /// </summary>
        /// <param name="password">待加密的字符串</param>
        /// <returns>返回的加密后的字符串</returns>
        private string MD5Encrypt32(string str)
        {
            HashAlgorithm md5Hasher = new SHA1Managed();
            byte[] hashedDataBytes;
            hashedDataBytes = md5Hasher.ComputeHash(Encoding.GetEncoding("gb2312").GetBytes(str));
            StringBuilder tmp = new StringBuilder();
            foreach (byte i in hashedDataBytes)
            {
                tmp.Append(i.ToString("x2"));
            }
            return tmp.ToString();//默认情况
        }

        private string GetArguments(HttpActionContext context)
        {
            var argumentsEncrypt = "arguments";
            if (this._includeArguments && context.ActionArguments != null)
            {
                return JsonConvert.SerializeObject(context.ActionArguments);
            }
            return argumentsEncrypt;
        }
    }
}

ApiRateLimit限流文档

  1. AspNetCoreRateLimit
  2. Microsoft.AspNetCore.RateLimiting
posted @ 2023-03-16 22:36  雨水的命运  阅读(137)  评论(0编辑  收藏  举报