• .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与服务器存入的不一样,或多次请求,验证不会通过,在此不再贴图演示了

      

  • 相关阅读:
    咏南微服务架构中间件
    几个开源的clickstream 分析工具
    partiql 学习一 试用
    partiql 亚马逊开源的基于sql 的查询语言
    cube.js 最新版本的一些特性
    astronomer 企业级的airflow 框架
    neodash 构建neo4j dashboard 的工具
    开发自定义的dremio 函数
    dremio sql 操作
    dremio 生产部署推荐硬件配置
  • 原文地址:https://www.cnblogs.com/personblog/p/14516806.html
Copyright © 2020-2023  润新知