重復(fù)提交表單攔截思路:
-
前端:設(shè)置一個(gè)全局變量=true,在提交表單函數(shù)開(kāi)頭判斷沙合,如果全局變量==false 則return 否則 設(shè)置全局變量=false奠伪;在接口回調(diào)函數(shù)中設(shè)置 全局變量=true。
var checkCallback = true;//防止頻繁點(diǎn)擊 //表單提交 function submit() { if (!checkCallback) { return;//回調(diào)函數(shù)回來(lái)前 不繼續(xù)往下執(zhí)行;也可以根據(jù)自己的需要給或不給用戶提示 } checkCallback = false; $.post('/SubmitContent', { }, function (res) {//這里就是回調(diào)函數(shù) checkCallback = true; console.log(res); }) }
-
后端:找到接口參數(shù)中能表示本次請(qǐng)求唯一性的參數(shù)值绊率,如:用戶id谨敛;在進(jìn)入接口函數(shù)前給用戶打標(biāo)記,接口函數(shù)響應(yīng)前取消用戶標(biāo)記滤否,可是使用靜態(tài)變量/緩存脸狸;【如果是緩存,用戶id +其他固定字符作為key藐俺,時(shí)間作為value】在接口函數(shù)入口判斷炊甲,如果用戶緩存標(biāo)記有 則return 接口響應(yīng)“頻繁請(qǐng)求提示” 否則 給用戶打標(biāo)記;接口函數(shù)響應(yīng)前 取消用戶標(biāo)記欲芹。
/// <summary> /// 接口函數(shù) /// </summary> /// <param name="userId">用戶id</param> /// <returns></returns> public ActionResult SubmitContent(string userId) { string _cacheKey = $"xxxx_{userId}"; if (CacheHelper.GetCache(_cacheKey) != null) { return Json("訪問(wèn)過(guò)于頻繁"); } CacheHelper.SetCache(_cacheKey, DateTime.Now); #region 業(yè)務(wù)處理代碼 #endregion CacheHelper.RemoveAllCache(_cacheKey); return Json(null); }
思路終歸是思路卿啡,能滿足基本要求,實(shí)際開(kāi)發(fā)過(guò)程中耀石,還需要對(duì)它進(jìn)行豐富牵囤;如前端設(shè)置連點(diǎn)3次以上給出提示,后端創(chuàng)建一個(gè)過(guò)濾器
再次完善滞伟,增加功能
<script type="text/javascript">
var checkCallback = 1;//防止頻繁點(diǎn)擊
//表單提交
function submit() {
//大于1 表示上一次請(qǐng)求還沒(méi)有返回
if (checkCallback > 1) {
if (checkCallback > 3) {
alert('正在提交中揭鳞,請(qǐng)稍后');
}
return;
}
checkCallback++;
$.post('/SubmitContent', {}, function (res) {
//由于本身是異步操作,防止用于在提示前 再次點(diǎn)擊提交梆奈;
setTimeout(() => { checkCallback = 1; }, 200);
console.log(res);
})
}
</script>
/// <summary>
/// 接口函數(shù)
/// </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("訪問(wèn)過(guò)于頻繁");
}
CacheHelper.SetCache(_cacheKey, DateTime.Now);
#region 業(yè)務(wù)處理代碼
#endregion
CacheHelper.RemoveAllCache(_cacheKey);
return Json(null);
}
我用到的是MVC架構(gòu)野崇,為了更好的服務(wù)于現(xiàn)有平臺(tái),再次對(duì)后端進(jìn)行了封裝優(yōu)化
/// <summary>
/// 上次請(qǐng)求未響應(yīng)前亩钟,不處理后來(lái)者的請(qǐng)求乓梨,防止重復(fù)提交
/// 1. 默認(rèn)一分鐘內(nèi)的請(qǐng)求只處理一次,超出后攔截放開(kāi)
/// 2. 最多可設(shè)置到二級(jí)參數(shù)
/// </summary>
public class PreventFrequentRequestAttribute : ActionFilterAttribute
{
public PreventFrequentRequestAttribute()
{ }
#region 可配置的屬性
/// <summary>
/// 接口用到的參數(shù)名稱
/// </summary>
public string ParamsName { get; set; } = "userId";
/// <summary>
/// 上個(gè)接口未返回前清酥,攔截最近多少秒的請(qǐng)求
/// </summary>
public int Seconds { get; set; } = 60;
/// <summary>
/// 接口響應(yīng)后扶镀,是否放開(kāi)攔截;可實(shí)現(xiàn)規(guī)定時(shí)間內(nèi)的控流操作
/// </summary>
public bool IsClear { get; set; } = true;
/// <summary>
/// 是否 是一個(gè)可轉(zhuǎn)JObject對(duì)象
/// </summary>
public bool IsJObject { get; set; } = false;
/// <summary>
/// 二級(jí)參數(shù)名稱焰轻,配合IsJObject使用
/// </summary>
public string UseParamsName { get; set; } = "userId";
#endregion
/// <summary>
/// 當(dāng)前請(qǐng)求詳細(xì)路徑
/// </summary>
private string m_thisActionPath;
/// <summary>
/// 帶有路徑的緩存key
/// </summary>
private string m_paramsCacheKey;
/// <summary>
/// 從請(qǐng)求上下文中 獲得要驗(yàn)證的表示參數(shù)值
/// </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>
/// 進(jìn)入處理函數(shù)前 增加訪問(wèn)記錄
/// </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}";
//設(shè)置單用戶頻繁請(qǐng)求攔截
m_paramsCacheKey = $"{m_thisActionPath}_{paramsValue}";
if (CacheHelper.GetCache(m_paramsCacheKey) != null)
{
var _result = new { Code = "-10", Msg = "您的請(qǐng)求太頻繁了臭觉,請(qǐng)稍后再試" };
filterContext.Result = new JsonResult() { Data = _result };
return;
}
CacheHelper.SetCache(m_paramsCacheKey, DateTime.Now, DateTime.Now.AddSeconds(Seconds));
}
base.OnActionExecuting(filterContext);
}
/// <summary>
/// 離開(kāi)函數(shù)前 移除訪問(wèn)記錄
/// </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);
}
}
無(wú)論使用的哪種框架,思路都是差不多的辱志,雖不說(shuō)能直接考慮蝠筑,但稍微修改下 就是可以直接使用的。