js和C# MVC架构 过滤器,防止重复提交,限流功能

重复提交表单拦截思路:

  1. 前端:设置一个全局变量=true,在提交表单函数开头判断,如果全局变量==false 则return 否则 设置全局变量=false;在接口回调函数中设置 全局变量=true。

         var checkCallback = true;//防止频繁点击
         //表单提交
         function submit() {
             if (!checkCallback) {
                 return;//回调函数回来前 不继续往下执行;也可以根据自己的需要给或不给用户提示
             }
             checkCallback = false;
             $.post('/SubmitContent', { }, function (res) {//这里就是回调函数
                 checkCallback = true;
                 console.log(res);
             })
         }
    
  2. 后端:找到接口参数中能表示本次请求唯一性的参数值,如:用户id;在进入接口函数前给用户打标记,接口函数响应前取消用户标记,可是使用静态变量/缓存;【如果是缓存,用户id +其他固定字符作为key,时间作为value】在接口函数入口判断,如果用户缓存标记有 则return 接口响应“频繁请求提示” 否则 给用户打标记;接口函数响应前 取消用户标记。

            /// <summary>
            /// 接口函数
            /// </summary>
            /// <param name="userId">用户id</param>
            /// <returns></returns>
            public ActionResult SubmitContent(string userId)
            {
                string _cacheKey = $"xxxx_{userId}";
                if (CacheHelper.GetCache(_cacheKey) != null)
                {
                    return Json("访问过于频繁");
                }
                CacheHelper.SetCache(_cacheKey, DateTime.Now);
    
                #region 业务处理代码
    
                #endregion
    
                CacheHelper.RemoveAllCache(_cacheKey);
    
                return Json(null);
            }
    

思路终归是思路,能满足基本要求,实际开发过程中,还需要对它进行丰富;如前端设置连点3次以上给出提示,后端创建一个过滤器

再次完善,增加功能

    <script type="text/javascript">
        var checkCallback = 1;//防止频繁点击
        //表单提交
        function submit() {
            //大于1 表示上一次请求还没有返回
            if (checkCallback > 1) {
                if (checkCallback > 3) {
                    alert('正在提交中,请稍后');
                }
                return;
            }
            checkCallback++;
            $.post('/SubmitContent', {}, function (res) {
                //由于本身是异步操作,防止用于在提示前 再次点击提交;
                setTimeout(() => { checkCallback = 1; }, 200);
                console.log(res);
            })
        }
    </script>
        /// <summary>
        /// 接口函数
        /// </summary>
        /// <param name="userId">用户id</param>
        /// <returns></returns>
        public ActionResult SubmitContent(string userId)
        {
            string _cacheKey = $"xxxx_{userId}";
            var _lastTime = CacheHelper.GetCache(_cacheKey);
            if (_lastTime != null && Convert.ToDateTime(_lastTime).AddMinutes(1) > DateTime.Now)
            {
                return Json("访问过于频繁");
            }
            CacheHelper.SetCache(_cacheKey, DateTime.Now);

            #region 业务处理代码

            #endregion

            CacheHelper.RemoveAllCache(_cacheKey);

            return Json(null);
        }

我用到的是MVC架构,为了更好的服务于现有平台,再次对后端进行了封装优化


    /// <summary>
    /// 上次请求未响应前,不处理后来者的请求,防止重复提交
    /// 1. 默认一分钟内的请求只处理一次,超出后拦截放开
    /// 2. 最多可设置到二级参数
    /// </summary>
    public class PreventFrequentRequestAttribute : ActionFilterAttribute
    {
        public PreventFrequentRequestAttribute()
        { }

        #region 可配置的属性

        /// <summary>
        /// 接口用到的参数名称
        /// </summary>
        public string ParamsName { get; set; } = "userId";
        /// <summary>
        /// 上个接口未返回前,拦截最近多少秒的请求
        /// </summary>
        public int Seconds { get; set; } = 60;
        /// <summary>
        /// 接口响应后,是否放开拦截;可实现规定时间内的控流操作
        /// </summary>
        public bool IsClear { get; set; } = true;
        /// <summary>
        /// 是否 是一个可转JObject对象
        /// </summary>
        public bool IsJObject { get; set; } = false;
        /// <summary>
        /// 二级参数名称,配合IsJObject使用
        /// </summary>
        public string UseParamsName { get; set; } = "userId";
        #endregion

        /// <summary>
        /// 当前请求详细路径
        /// </summary>
        private string m_thisActionPath;
        /// <summary>
        /// 带有路径的缓存key
        /// </summary>
        private string m_paramsCacheKey;

        /// <summary>
        /// 从请求上下文中 获得要验证的表示参数值
        /// </summary>
        /// <param name="filterContext"></param>
        /// <returns></returns>
        public string GetParamsValue(ActionExecutingContext filterContext)
        {
            if (filterContext.ActionParameters.Count == 0 || !filterContext.ActionParameters.ContainsKey(ParamsName))
                return null;

            var _paramsValue = filterContext.ActionParameters[ParamsName];
            if (IsJObject)
            {
                return Newtonsoft.Json.Linq.JObject.FromObject(_paramsValue)?[UseParamsName]?.ToString();
            }
            return _paramsValue.ToString();
        }

        /// <summary>
        /// 进入处理函数前 增加访问记录
        /// </summary>
        /// <param name="filterContext"></param>
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            string paramsValue = GetParamsValue(filterContext);

            if (!string.IsNullOrWhiteSpace(paramsValue))
            {
                m_thisActionPath = $"{filterContext.Controller}.{filterContext.ActionDescriptor.ActionName}";
                //设置单用户频繁请求拦截
                m_paramsCacheKey = $"{m_thisActionPath}_{paramsValue}";
                if (CacheHelper.GetCache(m_paramsCacheKey) != null)
                {
                    var _result = new { Code = "-10", Msg = "您的请求太频繁了,请稍后再试" };
                    filterContext.Result = new JsonResult() { Data = _result };
                    return;
                }
                CacheHelper.SetCache(m_paramsCacheKey, DateTime.Now, DateTime.Now.AddSeconds(Seconds));
            }

            base.OnActionExecuting(filterContext);
        }
        /// <summary>
        /// 离开函数前 移除访问记录
        /// </summary>
        /// <param name="filterContext"></param>
        public override void OnResultExecuting(ResultExecutingContext filterContext)
        {
            //(filterContext.Result as System.Web.Mvc.JsonResult).Data
            if (!string.IsNullOrWhiteSpace(m_paramsCacheKey) && IsClear)
                CacheHelper.RemoveAllCache(m_paramsCacheKey);

            base.OnResultExecuting(filterContext);
        }
    }

无论使用的哪种框架,思路都是差不多的,虽不说能直接考虑,但稍微修改下 就是可以直接使用的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343