js和C# MVC架构 过滤器,防止重复提交,限流功能
2020-08-28 本文已影响0人
小船翻不翻
重复提交表单拦截思路:
-
前端:设置一个全局变量=true,在提交表单函数开头判断,如果全局变量==false 则return 否则 设置全局变量=false;在接口回调函数中设置 全局变量=true。
var checkCallback = true;//防止频繁点击 //表单提交 function submit() { if (!checkCallback) { return;//回调函数回来前 不继续往下执行;也可以根据自己的需要给或不给用户提示 } checkCallback = false; $.post('/SubmitContent', { }, function (res) {//这里就是回调函数 checkCallback = true; console.log(res); }) }
-
后端:找到接口参数中能表示本次请求唯一性的参数值,如:用户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);
}
}
无论使用的哪种框架,思路都是差不多的,虽不说能直接考虑,但稍微修改下 就是可以直接使用的。