一个通用的计数限制解决方案

本贴最后更新于 1982 天前,其中的信息可能已经天翻地覆

场景:


 1,一个用户在一天内最多修改 3 次密码;

2,一个手机号在五分钟内只能接受 2 次验证码;

3,一个用户在一个月内只能置顶一篇文章;

。。。。。。

像这样的场景应该是很常见的,每个系统中都可能用到,一个系统中可能多个地方都会用到,所以有必要设计一个通用的方案来处理这种问题。

分析其本质,可以用一句话来概括:一个事件在一个时间段内最多可以执行特定次数。

 

模型设计: 


 

 /// <summary>
    /// 计数限制 /// </summary>
    public class CountLimit
    {
        [OrmLiteField(IsPrimaryKey = true, InsertIgnore = true)] public long Id { get; set; } /// <summary>
        /// 唯一标识 /// </summary>
        public string UniqueId { get; set; } /// <summary>
        /// 用途 /// </summary>
        public byte UseType { get; set; } /// <summary>
        /// 已有计数 /// </summary>
        public int AlreadyCount { get; set; } /// <summary>
        /// 开始计数时间 /// </summary>
        public DateTime BeginTime { get; set; }
    }

模型中使用 UniqueId 和 UseType 来确定一个事件,比如手机号 18500000000 接受验证码,18500000000 是 UniqueId,接受验证码是 UseType=1;

BeginTime 是开始计时时间,如果是每个自然天的话,可能就是 2018-5-11 00:00:00,注意,这么没有结束时间;

AlreadyCount 是从计时开始,已经发生的计数;

 

持久层方法:


 这里的持久化使用 sqlserver 数据库,也可以使用类似 redis 的缓存数据库,如果那样的话,可以稍微改一下模型,把 uniqueId 和 useType 合并成一个键,来代替 id,这个就不多说了,用者自知。

 public class D_CountLimit
    { public static CountLimit Single(byte useType, string uniqueId)
        { using (var db = DB.Open())
            { return db.SingleWhere<CountLimit>(DictBuilder.Assign("UniqueId", uniqueId).Assign("UseType", useType));
            }
        } public static long Add(CountLimit m)
        { using (var db = DB.Open())
            { return db.Insert(m);
            }
        } public static int IncCount(long id)
        { using (var db = DB.Open())
            { return db.UpdateFieldById<CountLimit>("$AlreadyCount", "AlreadyCount+1", id);
            }
        } public static int ResetOne(long id, DateTime beginTime)
        { using (var db = DB.Open())
            { return db.UpdateById<CountLimit>(DictBuilder.Assign("AlreadyCount", 1).Assign("BeginTime", beginTime), id);
            }
        }
    }

 

通用控制逻辑:


CheckLimit 方法最后的三个参数,就是通用性的来源,上面说模型中没有结束时间,是因为控制权在最后两个参数上。

比如时间段是自然天,那么 duration=TimeSpan.FromDays(1),beginTime=DateTime.Now.Date(如果从触发时起 24 小时之后为一天,beginTime=DateTime.Now 即可,其他周期类推);

    public class C_CountLimit
    { /// <summary>
        /// 检测计数公共方法 /// </summary>
        /// <param name="useType">使用类型</param>
        /// <param name="uniqueId">特定使用类型中唯一标识</param>
        /// <param name="limit">限制次数</param>
        /// <param name="duration">时间间隔</param>
        /// <param name="beginTime">当前间隔开始时间</param>
        /// <returns></returns>
        public static bool CheckLimit(byte useType, string uniqueId, int limit, TimeSpan duration, DateTime? beginTime = null)
        { if (beginTime == null)
            {
                beginTime = DateTime.Now;
            } var m = D_CountLimit.Single(useType, uniqueId); //还没计数记录
            if (m == null)
            {
                D_CountLimit.Add(new CountLimit
                {
                    AlreadyCount = 1,
                    BeginTime = beginTime.Value,
                    UniqueId = uniqueId,
                    UseType = useType
                });
            } else { //时间范围之内
                if (m.BeginTime <= DateTime.Now && m.BeginTime.Add(duration) > DateTime.Now)
                { //计数已满
                    if (m.AlreadyCount >= limit)
                    { return false;
                    } else { //增加计数
 D_CountLimit.IncCount(m.Id);
                    }
                } else { //不在时间范围之内,设置为一次计数
 D_CountLimit.ResetOne(m.Id, beginTime.Value);
                }
            } return true;
        }
    }

 

测试:


 下面的例子是检测 10 秒内 18500000000 这个手机号只能发 3 次验证码(假设 useType=1)

    while (true)
    { var result = C_CountLimit.CheckLimit(1, "18500000000", 3, TimeSpan.FromSeconds(10), DateTime.Now);
        Console.WriteLine(result);
        Thread.Sleep(1000);
    }

由于我们一秒执行一次,下图和我们的预期正好相符:

58668201805111111187661880570976.jpg

有些情况并没有什么时间周期,而是在整个系统之内只能执行有限的次数,这时只需把 duration 设置的大一些就行了,比如设置为 TimeSpan.FromDays(365000),千年轮回,在 1000 年之后这个系统存不存在便是个问题了!

  • .NET
    27 引用 • 6 回帖 • 5 关注
  • 技巧
    11 引用 • 35 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
  • someone
    作者

    实现项目中多次使用,屡试不爽,给自己点个赞 👍

推荐标签 标签

  • RESTful

    一种软件架构设计风格而不是标准,提供了一组设计原则和约束条件,主要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。

    30 引用 • 114 回帖 • 2 关注
  • FlowUs

    FlowUs.息流 个人及团队的新一代生产力工具。

    让复杂的信息管理更轻松、自由、充满创意。

    1 引用 • 2 关注
  • Notion

    Notion - The all-in-one workspace for your notes, tasks, wikis, and databases.

    6 引用 • 38 回帖
  • 单点登录

    单点登录(Single Sign On)是目前比较流行的企业业务整合的解决方案之一。SSO 的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。

    9 引用 • 25 回帖 • 5 关注
  • 酷鸟浏览器

    安全 · 稳定 · 快速
    为跨境从业人员提供专业的跨境浏览器

    3 引用 • 59 回帖 • 28 关注
  • 数据库

    据说 99% 的性能瓶颈都在数据库。

    343 引用 • 719 回帖
  • FFmpeg

    FFmpeg 是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。

    23 引用 • 32 回帖 • 2 关注
  • 创造

    你创造的作品可能会帮助到很多人,如果是开源项目的话就更赞了!

    179 引用 • 996 回帖
  • GAE

    Google App Engine(GAE)是 Google 管理的数据中心中用于 WEB 应用程序的开发和托管的平台。2008 年 4 月 发布第一个测试版本。目前支持 Python、Java 和 Go 开发部署。全球已有数十万的开发者在其上开发了众多的应用。

    14 引用 • 42 回帖 • 766 关注
  • DevOps

    DevOps(Development 和 Operations 的组合词)是一组过程、方法与系统的统称,用于促进开发(应用程序/软件工程)、技术运营和质量保障(QA)部门之间的沟通、协作与整合。

    48 引用 • 25 回帖 • 3 关注
  • 前端

    前端技术一般分为前端设计和前端开发,前端设计可以理解为网站的视觉设计,前端开发则是网站的前台代码实现,包括 HTML、CSS 以及 JavaScript 等。

    247 引用 • 1348 回帖
  • CAP

    CAP 指的是在一个分布式系统中, Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可兼得。

    11 引用 • 5 回帖 • 609 关注
  • 又拍云

    又拍云是国内领先的 CDN 服务提供商,国家工信部认证通过的“可信云”,乌云众测平台认证的“安全云”,为移动时代的创业者提供新一代的 CDN 加速服务。

    21 引用 • 37 回帖 • 546 关注
  • Sphinx

    Sphinx 是一个基于 SQL 的全文检索引擎,可以结合 MySQL、PostgreSQL 做全文搜索,它可以提供比数据库本身更专业的搜索功能,使得应用程序更容易实现专业化的全文检索。

    1 引用 • 217 关注
  • VirtualBox

    VirtualBox 是一款开源虚拟机软件,最早由德国 Innotek 公司开发,由 Sun Microsystems 公司出品的软件,使用 Qt 编写,在 Sun 被 Oracle 收购后正式更名成 Oracle VM VirtualBox。

    10 引用 • 2 回帖 • 2 关注
  • SVN

    SVN 是 Subversion 的简称,是一个开放源代码的版本控制系统,相较于 RCS、CVS,它采用了分支管理系统,它的设计目标就是取代 CVS。

    29 引用 • 98 回帖 • 685 关注
  • 招聘

    哪里都缺人,哪里都不缺人。

    190 引用 • 1057 回帖
  • Shell

    Shell 脚本与 Windows/Dos 下的批处理相似,也就是用各类命令预先放入到一个文件中,方便一次性执行的一个程序文件,主要是方便管理员进行设置或者管理用的。但是它比 Windows 下的批处理更强大,比用其他编程程序编辑的程序效率更高,因为它使用了 Linux/Unix 下的命令。

    123 引用 • 74 回帖
  • 游戏

    沉迷游戏伤身,强撸灰飞烟灭。

    177 引用 • 816 回帖 • 1 关注
  • 以太坊

    以太坊(Ethereum)并不是一个机构,而是一款能够在区块链上实现智能合约、开源的底层系统。以太坊是一个平台和一种编程语言 Solidity,使开发人员能够建立和发布下一代去中心化应用。 以太坊可以用来编程、分散、担保和交易任何事物:投票、域名、金融交易所、众筹、公司管理、合同和知识产权等等。

    34 引用 • 367 回帖
  • Kotlin

    Kotlin 是一种在 Java 虚拟机上运行的静态类型编程语言,由 JetBrains 设计开发并开源。Kotlin 可以编译成 Java 字节码,也可以编译成 JavaScript,方便在没有 JVM 的设备上运行。在 Google I/O 2017 中,Google 宣布 Kotlin 成为 Android 官方开发语言。

    19 引用 • 33 回帖 • 62 关注
  • webpack

    webpack 是一个用于前端开发的模块加载器和打包工具,它能把各种资源,例如 JS、CSS(less/sass)、图片等都作为模块来使用和处理。

    41 引用 • 130 回帖 • 259 关注
  • 旅游

    希望你我能在旅途中找到人生的下一站。

    90 引用 • 899 回帖
  • RYMCU

    RYMCU 致力于打造一个即严谨又活泼、专业又不失有趣,为数百万人服务的开源嵌入式知识学习交流平台。

    4 引用 • 6 回帖 • 54 关注
  • 大数据

    大数据(big data)是指无法在一定时间范围内用常规软件工具进行捕捉、管理和处理的数据集合,是需要新处理模式才能具有更强的决策力、洞察发现力和流程优化能力的海量、高增长率和多样化的信息资产。

    93 引用 • 113 回帖
  • RabbitMQ

    RabbitMQ 是一个开源的 AMQP 实现,服务器端用 Erlang 语言编写,支持多种语言客户端,如:Python、Ruby、.NET、Java、C、PHP、ActionScript 等。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。

    49 引用 • 60 回帖 • 365 关注
  • Logseq

    Logseq 是一个隐私优先、开源的知识库工具。

    Logseq is a joyful, open-source outliner that works on top of local plain-text Markdown and Org-mode files. Use it to write, organize and share your thoughts, keep your to-do list, and build your own digital garden.

    6 引用 • 63 回帖 • 4 关注