一个程序员自学中

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

2020-08-28  本文已影响0人  小船翻不翻

重复提交表单拦截思路:

  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);
        }
    }

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

上一篇下一篇

猜你喜欢

热点阅读