热门标签 | HotTags
当前位置:  开发笔记 > 后端 > 正文

.NetCore利用Redis实现对接口访问次数限制

前言在工作中,我们会有让客户对某一接口或某一项功能,需要限制使用的次数,比如获取某个数据的API,下载次数等这类需求。这里我们封装限制接口,使用Redis实现。​实现首先,咱们新建


前言

在工作中,我们会有让客户对某一接口或某一项功能,需要限制使用的次数,比如获取某个数据的API,下载次数等这类需求。这里我们封装限制接口,使用Redis实现。


实现

首先,咱们新建一个空白解决方案RedisLimitDemo
image.png
新建抽象类库Limit.Abstractions
image.png
image.png

新建特性RequiresLimitAttribute,来进行限制条件设置。
咱们设定了LimitName限制名称,LimitSecond限制时长,LimitCount限制次数。

using System;
namespace Limit.Abstractions
{
///


/// 限制特性
///

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class RequiresLimitAttribute : Attribute
{
///
/// 限制名称
///

public string LimitName { get; }
///
/// 限制时长(秒)
///

public int LimitSecond { get; }
///
/// 限制次数
///

public int LimitCount { get; }
public RequiresLimitAttribute(string limitName, int limitSecOnd= 1, int limitCount = 1)
{
if (string.IsNullOrWhiteSpace(limitName))
{
throw new ArgumentNullException(nameof(limitName));
}
LimitName = limitName;
LimitSecOnd= limitSecond;
LimitCount = limitCount;
}
}
}

新建异常类LimitValidationFailedException对超出次数的功能抛出统一的异常,这样利于管理。

using System;
namespace Limit.Abstractions
{
///


/// 限制验证失败异常
///

public class LimitValidationFailedException : Exception
{
public LimitValidationFailedException(string limitName, int limitCount)
: base($"功能{limitName}已到最大使用上限{limitCount}!")
{
}
}
}

新建上下文RequiresLimitContext类,用于各个方法之间,省的需要各种拼装参数,直接一次到位。

namespace Limit.Abstractions
{
///


/// 限制验证上下文
///

public class RequiresLimitContext
{
///
/// 限制名称
///

public string LimitName { get; }
///
/// 默认限制时长(秒)
///

public int LimitSecond { get; }
///
/// 限制次数
///

public int LimitCount { get; }
// 其它
public RequiresLimitContext(string limitName, int limitSecond, int limitCount)
{
LimitName = limitName;
LimitSecOnd= limitSecond;
LimitCount = limitCount;
}
}
}

封装验证限制次数的接口IRequiresLimitChecker,方便咱们进行各种实现,面向接口开发!

using System.Threading;
using System.Threading.Tasks;
namespace Limit.Abstractions
{
public interface IRequiresLimitChecker
{
///


/// 验证
///

///


///


///
Task CheckAsync(RequiresLimitContext context, CancellationToken cancellation = default);
///


///
///

///


///


///
Task ProcessAsync(RequiresLimitContext context, CancellationToken cancellation = default);
}
}

现在,咱们具备了实现限制验证的所有条件,但咱们选择哪种方法进行验证呢?可以使用AOP动态代理,或者使用MVC的过滤器
这里,咱们方便演示,就使用IAsyncActionFilter过滤器接口进行实现。

新建LimitValidationAsyncActionFilter限制验证过滤器。

using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using System.Reflection;
using System.Threading.Tasks;
namespace Limit.Abstractions
{
///


/// 限制验证过滤器
///

public class LimitValidationAsyncActionFilter : IAsyncActionFilter
{
public IRequiresLimitChecker RequiresLimitChecker { get; }
public LimitValidationAsyncActionFilter(IRequiresLimitChecker requiresLimitChecker)
{
RequiresLimitChecker = requiresLimitChecker;
}
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
// 获取特性
var limitAttribute = GetRequiresLimitAttribute(GetMethodInfo(context));
if (limitAttribute == null)
{
await next();
return;
}
// 组装上下文
var requiresLimitCOntext= new RequiresLimitContext(limitAttribute.LimitName, limitAttribute.LimitSecond, limitAttribute.LimitCount);
// 检查
await PreCheckAsync(requiresLimitContext);
// 执行方法
await next();
// 次数自增
await PostCheckAsync(requiresLimitContext);
}
protected virtual MethodInfo GetMethodInfo(ActionExecutingContext context)
{
return (context.ActionDescriptor as ControllerActionDescriptor).MethodInfo;
}
///
/// 获取限制特性
///

///
protected virtual RequiresLimitAttribute GetRequiresLimitAttribute(MethodInfo methodInfo)
{
return methodInfo.GetCustomAttribute();
}
///
/// 验证之前
///

///


///
protected virtual async Task PreCheckAsync(RequiresLimitContext context)
{
bool isAllowed = await RequiresLimitChecker.CheckAsync(context);
if (!isAllowed)
{
throw new LimitValidationFailedException(context.LimitName, context.LimitCount);
}
}
///


/// 验证之后
///

///


///
protected virtual async Task PostCheckAsync(RequiresLimitContext context)
{
await RequiresLimitChecker.ProcessAsync(context);
}
}
}

逻辑看起来非常简单。
首先,咱们需要判断执行的方法是否进行了限制,就是有没有标注RequiresLimitAttribute这个特性,如果没有就直接执行。否则的话,咱们需要在执行方法之前,判断是否能执行方法,执行之后需要让使用次数进行+1操作。

上面就是基础的实现,接下来咱们需要接入Redis,实现具体的判断和使用次数自增。

新建类库Limit.Redis
image.png
image.png
新建选项类RedisRequiresLimitOptions,因为咱们也不知道Redis连接方式啊,这样就需要在使用的时候进行配置。

using Microsoft.Extensions.Options;
namespace Limit.Redis
{
public class RedisRequiresLimitOptions : IOptions
{
///


/// Redis连接字符串
///

public string Configuration { get; set; }
///
/// Key前缀
///

public string Prefix { get; set; }
public RedisRequiresLimitOptions Value => this;
}
}

这里,咱们使用了Configuration来进行配置连接字符串,有时候咱们需要对Key加上前缀,方便查找或者进行模块划分,所以又需要Prefix前缀。

有了配置,就可以连接Redis了!
但是连接Redis也得需要方式,这里咱们使用开源类库StackExchange.Redis来进行操作。

新建实现类RedisRequiresLimitChecker

using Limit.Abstractions;
using Microsoft.Extensions.Options;
using StackExchange.Redis;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Limit.Redis
{
public class RedisRequiresLimitChecker : IRequiresLimitChecker
{
protected RedisRequiresLimitOptions Options { get; }
private IDatabaseAsync _database;
private readonly SemaphoreSlim _cOnnectionLock= new SemaphoreSlim(initialCount: 1, maxCount: 1);
public RedisRequiresLimitChecker(IOptions options)
{
if (optiOns== null)
{
throw new ArgumentNullException(nameof(options));
}
OptiOns= options.Value;
}
public async Task CheckAsync(RequiresLimitContext context, CancellationToken cancellation = default)
{
await ConnectAsync();
if (await _database.KeyExistsAsync(CalculateCacheKey(context)))
{
var result = await _database.StringGetAsync(CalculateCacheKey(context));
return (int)result + 1 <= context.LimitCount;
}
else
{
return true;
}
}
public async Task ProcessAsync(RequiresLimitContext context, CancellationToken cancellation = default)
{
await ConnectAsync();
string cacheKey = CalculateCacheKey(context);
if (await _database.KeyExistsAsync(cacheKey))
{
await _database.StringIncrementAsync(cacheKey);
}
else
{
await _database.StringSetAsync(cacheKey, "1", new TimeSpan(0, 0, context.LimitSecond), When.Always);
}
}
protected virtual string CalculateCacheKey(RequiresLimitContext context)
{
return $"{Options.Prefix}f:RedisRequiresLimitChecker,ln:{context.LimitName}";
}
protected virtual async Task ConnectAsync(CancellationToken cancellation = default)
{
cancellation.ThrowIfCancellationRequested();
if (_database != null)
{
return;
}
// 控制并发
await _connectionLock.WaitAsync(cancellation);
try
{
if (_database == null)
{
var cOnnection= await ConnectionMultiplexer.ConnectAsync(Options.Configuration);
_database = connection.GetDatabase();
}
}
finally
{
_connectionLock.Release();
}
}
}
}

逻辑也是简单的逻辑,也不多解释了。不过这里的命令执行起来可能会有间隙,在高并发的情况下,先这样吧。

实现咱们有了,接下来就要写扩展方法方便咱们调用了。
新建扩展方法类ServiceCollectionExtensions,记得命名空间要在Microsoft.Extensions.DependencyInjection下面,不然使用的时候找这个方法也是一个问题!

using Limit.Abstractions;
using Limit.Redis;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection.Extensions;
using System;
namespace Microsoft.Extensions.DependencyInjection
{
public static class ServiceCollectionExtensions
{
///


/// 添加Redis功能限制验证
///

///


///


public static void AddRedisLimitValidation(this IServiceCollection services, Action options)
{
services.Replace(ServiceDescriptor.Singleton());
services.Configure(options);
services.Configure(mvcOptiOns=>
{
mvcOptions.Filters.Add();
});
}
}
}

至此,全部结束,我们要进行验证。

新建.Net Core Web API项目LimitTestWebApi
image.png
image.png
引入咱们写好的类库Limit.Redis

然后在Program类中,注入写好的服务。
image.png
直接就用模板自带的Controller进行测试把
image.png
image.png
咱们让他60秒内只能访问5次!

启动项目开始测试!
image.png
首先执行一次。
image.png
查看Redis中的数据。
image.png
再快速执行5次。
image.png
Redis中数据。
image.png
缓存剩余时间。
image.png
咱们等到时间再次执行。
image.png
ok,完成!

本次演示代码 :https://github.com/applebananamilk/RedisLimitDemo



推荐阅读
  • 本文介绍了Redis的基础数据结构string的应用场景,并以面试的形式进行问答讲解,帮助读者更好地理解和应用Redis。同时,描述了一位面试者的心理状态和面试官的行为。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • SpringBoot uri统一权限管理的实现方法及步骤详解
    本文详细介绍了SpringBoot中实现uri统一权限管理的方法,包括表结构定义、自动统计URI并自动删除脏数据、程序启动加载等步骤。通过该方法可以提高系统的安全性,实现对系统任意接口的权限拦截验证。 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • 本文详细介绍了云服务器API接口的概念和作用,以及如何使用API接口管理云上资源和开发应用程序。通过创建实例API、调整实例配置API、关闭实例API和退还实例API等功能,可以实现云服务器的创建、配置修改和销毁等操作。对于想要学习云服务器API接口的人来说,本文提供了详细的入门指南和使用方法。如果想进一步了解相关知识或阅读更多相关文章,请关注编程笔记行业资讯频道。 ... [详细]
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
  • 20211101CleverTap参与度和分析工具功能平台学习/实践
    1.应用场景主要用于学习CleverTap的使用,该平台主要用于客户保留与参与平台.为客户提供价值.这里接触到的原因,是目前公司用到该平台的服务~2.学习操作 ... [详细]
  • 如何用UE4制作2D游戏文档——计算篇
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了如何用UE4制作2D游戏文档——计算篇相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • 使用在线工具jsonschema2pojo根据json生成java对象
    本文介绍了使用在线工具jsonschema2pojo根据json生成java对象的方法。通过该工具,用户只需将json字符串复制到输入框中,即可自动将其转换成java对象。该工具还能解析列表式的json数据,并将嵌套在内层的对象也解析出来。本文以请求github的api为例,展示了使用该工具的步骤和效果。 ... [详细]
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • 本文讨论了在使用Timer控件和键盘触发时可能出现的冲突问题,并提供了解决方法。同时还介绍了如何实现一个类似QQ的小图标只出现在右下角而不在状态栏的程序。 ... [详细]
  • Voicewo在线语音识别转换jQuery插件的特点和示例
    本文介绍了一款名为Voicewo的在线语音识别转换jQuery插件,该插件具有快速、架构、风格、扩展和兼容等特点,适合在互联网应用中使用。同时还提供了一个快速示例供开发人员参考。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
author-avatar
水晶玲珑9261996
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有