.netcore WebApi接口 防止按鈕重復點擊


 

  • 輔助服務,redisHelper類
using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace  MyWebApi.Service
{
    public interface IRedisService
    {
        Task<bool> StringSetAsync(string key, string value, TimeSpan? span);
        Task<bool> LockTakeAsync(string key, string value, TimeSpan span, string prefix = "locker:");
        Task<bool> LockReleaseAsync(string key, string value, string prefix = "locker:");
        Task<string> StringGetAsync(string key);
        Task<bool> KeyDeleteAsync(string key);
        Task<bool> KeyExistsAsync(string key);
        Task<bool> FuzzySearchExistsAsync(string prefix,string merchantId);
    }
    public class RedisService : IRedisService
    {
        IDatabase _redis;
        public RedisService(IDatabase redis)
        {
            _redis = redis;
        }
        public async Task<bool> KeyDeleteAsync(string key)
        {
            return await _redis.KeyDeleteAsync(key);
        }

        public async Task<bool> KeyExistsAsync(string key)
        {
            return await _redis.KeyExistsAsync(key);
        }

        public async Task<bool> StringSetAsync(string key, string value, TimeSpan? span)
        {
            return await _redis.StringSetAsync(key, value, span);
        }
        public async Task<string> StringGetAsync(string key)
        {
            return await _redis.StringGetAsync(key);
        }
        public async Task<bool> LockTakeAsync(string key, string value, TimeSpan span, string prefix = "locker:")
        {
            return await _redis.LockTakeAsync(prefix + key, value, span);
        }
        public async Task<bool> LockReleaseAsync(string key, string value, string prefix = "locker:")
        {
            return await _redis.LockReleaseAsync(prefix + key, value);
        }
        public async Task<bool> FuzzySearchExistsAsync(string prefix,string UserId) 
        {
            var pattern = $"{prefix}:{UserId}*";
            var redisResult =await _redis.ScriptEvaluateAsync(LuaScript.Prepare(
                         //Redis的keys模糊查詢:
                         " local res = redis.call('KEYS', @keypattern) " +
                         " return res "), new { @keypattern = pattern });
            string[] preSult = (string[])redisResult;//將返回的結果集轉為數組
            return preSult.Length > 0;
        }
    }
}
  • 創建action執行完成后,執行ResultFilterAttribute過濾器,返回multiclick值
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using MyWebApi.Service;
using Microsoft.AspNetCore.Http.Internal;
using Microsoft.AspNetCore.Mvc.Filters;
using Newtonsoft.Json;

namespace MyWebApi.Filters
{
    public class MulticlickHeader : ResultFilterAttribute
    {
        IRedisService _redisService;
        string _dependencyKey;
        public MulticlickHeader(string dependencyKey, IRedisService redisService)
        {
            _redisService = redisService;
            _dependencyKey = dependencyKey;
        }

        public override async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
        {
            if (!string.IsNullOrEmpty(_dependencyKey))
            {
                string[] dependencies = _dependencyKey.Split(':');
                string dependencyType = dependencies[0];
                string dependencySource = dependencies[1];
                string dependencyWhenReturnkey = dependencies[2];
                var needReturnKey = "";
                if (dependencyType == "query")
                {
                    needReturnKey = context.HttpContext.Request.Query[dependencySource];
                }
                else if (dependencyType == "body")
                {
                    context.HttpContext.Request.EnableRewind();
                    context.HttpContext.Request.Body.Seek(0, 0);
                    using (var ms = new MemoryStream())
                    {
                        context.HttpContext.Request.Body.CopyTo(ms);
                        var b = ms.ToArray();
                        var body = Encoding.UTF8.GetString(b);
                        needReturnKey = JsonConvert.DeserializeAnonymousType(body, new Dictionary<string, object>())[dependencySource].ToString();
                    }
                }
                if (needReturnKey.ToUpper() == dependencyWhenReturnkey.ToUpper())
                {
                    await GenerateHeader(context);
                }
            }
            else
            {
                await GenerateHeader(context);
            }
            //添加自定義header,返回給前端
            context.HttpContext.Response.Headers.Add("custom_headers",new string[]{"Origin","Accept","Content-Type","Date","multiclick"});
            //Access-Control-Expose-Headers作用是,里面的參數值能被前端獲取到
            context.HttpContext.Response.Headers.Add("Access-Control-Expose-Headers", "Origin,Accept,Content-Type,Date,multiclick");
            var resultContext = await next();
        }

        private async Task GenerateHeader(ResultExecutingContext context)
        {
            var value = Guid.NewGuid().ToString("N");
            if (await _redisService.StringSetAsync(value, "10", TimeSpan.FromDays(1)))
            {
                context.HttpContext.Response.Headers.Add("multiclick", new string[] {value});
            }
        }
    }
}
  •  創建Action方法執行前,multikey驗證過濾器MulticlickValidateFilter
 
using MyWebApi.Service;
using Microsoft.AspNetCore.Http.Internal;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
 

namespace MyWebApi.Filters
{
    public class MulticlickValidateFilter : IAsyncActionFilter
    {
        IRedisService _redisService;
        string _dependencyKey;
        public MulticlickValidateFilter(string dependencyKey, IRedisService redisService)
        {
            _redisService = redisService;
            _dependencyKey = dependencyKey;
        }
        public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
        {
            if (!string.IsNullOrEmpty(_dependencyKey))
            {
                string[] dependencies = _dependencyKey.Split(':');
                string dependencyType = dependencies[0];
                string dependencySource = dependencies[1];
                string dependencyWhenReturnkey = dependencies[2];
                var needReturnKey = "";
                if (dependencyType == "query")
                {
                    needReturnKey = context.HttpContext.Request.Query[dependencySource];
                }
                else if (dependencyType == "body")
                {
                    context.HttpContext.Request.EnableRewind();
                    context.HttpContext.Request.Body.Seek(0, 0);
                    using (var ms = new MemoryStream())
                    {
                        context.HttpContext.Request.Body.CopyTo(ms);
                        var b = ms.ToArray();
                        var body = Encoding.UTF8.GetString(b);
                        needReturnKey = JsonConvert.DeserializeAnonymousType(body, new Dictionary<string, object>())[dependencySource].ToString();
                    }
                }
                if (needReturnKey.ToUpper() == dependencyWhenReturnkey.ToUpper())
                {
                    await Validate(context, next);
                }
                else
                {
                    var resultContext = await next();
                }
            }
            else
            {
                await Validate(context, next);
            }

        }

        private async Task Validate(ActionExecutingContext context, ActionExecutionDelegate next)
        {
            var ticket = context.HttpContext.Request.Headers["multiclick"];
            if (string.IsNullOrEmpty(ticket))
            {
                context.Result = new JsonResult(  new { code=-1,msg= "無法獲取請求ticket" });
                return;
            }

            if (await _redisService.LockTakeAsync(ticket, ticket, TimeSpan.FromDays(1)))
            {
                if (await _redisService.StringGetAsync(ticket) != "100")
                {
                    context.Result = new JsonResult(new { code = -1, msg = "ticket失效,請刷新頁面重新獲取" } );
                    return;
                }

                var resultContext = await next();
                await _redisService.StringSetAsync(ticket, "200", TimeSpan.FromDays(1));
                await _redisService.LockReleaseAsync(ticket, ticket);
            }
            else
            {
                context.Result = new JsonResult(new { code = -1, msg = "處理中,請勿重復點擊" });
                return;
            }
        }
    }
}
  • 在Startup.cs中注入服務
  public void ConfigureServices(IServiceCollection services)
        {

            services.AddScoped<MulticlickValidateFilter>();
            services.AddScoped<MulticlickHeader>();

 

  • 例如,某一賬單 按鈕需要防止用戶重復點擊,那么在這個列表展示時,我們把multikey傳給瀏覽器,用戶支付時再把multikey傳給后端接口
  MulticlickHeader作用是生成唯一key,初始化狀態為100存入redis后,反回給瀏覽器
  /// <summary>
        /// 賬單列表
        /// </summary>
        /// <param name="info"></param>
        /// <returns></returns>
        [HttpPost("ListOrder")]
        [TypeFilter(typeof(MulticlickHeader), Arguments = new object[] { "" })]
        public async Task<BaseJsonResult> SearchFinance([FromBody] SearchInfo info)
        {

 

   如支付時,get請求參數值從query中獲取,post請求參數值從body中獲取

        [HttpGet("PayOrder")]
        [Consumes("application/json")]
        [TypeFilter(typeof(MulticlickHeader), Arguments = new object[] { "query:isSaveDb:false" })]
        [TypeFilter(typeof(MulticlickValidateFilter), Arguments = new object[] { "query:isSaveDb:true" })]
        public async Task<WebApiResult> PayOrder(string order_id, bool isSaveDb)
        {
        [HttpPost("PayOrder")]
        [Consumes("application/json")]
        [TypeFilter(typeof(MulticlickHeader), Arguments = new object[] { "body:isSaveDb:false" })]
        [TypeFilter(typeof(MulticlickValidateFilter), Arguments = new object[] { "body:isSaveDb:true" })]
        public async Task<WebApiResult> PayOrder(string order_id, bool isSaveDb)
        {

   支付時帶着Multiclickheader作用是防止,支付過程中再次生成新的key,影響前端瀏覽器傳過來的值失效問題

  獲取multiclick值效果圖

  如支付請求時,傳入的key與服務器存入的不一樣,或多次請求,驗證不會通過,在此不再貼圖演示了

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM