一、背景 

代码实例:https://gitee.com/d_c_l/curtainetcaop.git
我们实际系统中有很多操作,是不管做多少次,都应该产生一样的效果或返回一样的结果。 
例如:
 

1. 前端重复提交选中的数据,应该后台只产生对应这个数据的一个反应结果。 
2. 我们发起一笔付款请求,应该只扣用户账户一次钱,当遇到网络重发或系统bug重发,也应该只扣一次钱; 
3. 发送消息,也应该只发一次,同样的短信发给用户,用户会哭的; 
4. 创建业务订单,一次业务请求只能创建一个,创建多个就会出大问题。 

等等很多重要的情况,这些逻辑都需要幂等的特性来支持。 

二、幂等性概念
 
幂等(idempotent、idempotence)是一个数学与计算机学概念,常见于抽象代数中。 

在编程中.一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。幂等函数,或幂等方法,是指可以使用相同参数重复执行,并能获得相同结果的函数。这些函数不会影响系统状态,也不用担心重复执行会对系统造成改变。例如,“getusername()和settrue()”函数就是一个幂等函数. 

更复杂的操作幂等保证是利用唯一交易号(流水号)实现. 

我的理解:幂等就是一个操作,不论执行多少次,产生的效果和返回的结果都是一样的 

一,正式开始了

现在我们们要做一个点击按钮之后等待后台返回之后才可以再次请求方法,其他的重复请求直接进行拦截

在这里我使用拦截器进行实现(也可以使用中间件来实现),主要就是以aop(切面编程是面向对象的优化),将一些紧密的业务进行切开,在中间进行自己的一些逻辑处理

主要的实现思路就是使用toekn+redis缓存进行(当我们访问某个方法的时候给用户的客户端存取一个cookie)cookie里面主要是存的toekn,当我们访问接口的时候一定要带上有效的

token才能访问方法或者控制器。

客户端访问方法的时候存token到redis里面,当请求方法的时候带上token,拦截器里面判断是不是存放在redis里面的token如果不是的直接拦截返回,如果带上的是有效的token删除旧token

生成一个新的token存放在cookie里面,存到redis,给第二次请求发放token。

代码理解:

 

//视图页面
    public partial class homecontroller : controller
    {
        private readonly redishelp cache = new redishelp();
        /// <summary>
        /// 首页  获取token
        /// </summary>
        /// <returns></returns>
        [nosign]
        public iactionresult index()
        {
            string token = guid.newguid().tostring();
            httpcontext.response.cookies.append("token", token);
            cache.setvalue("token", token);
            return view();
        }
    }

 

当浏览器访问这个视图的时候给他发放一个token。

这里存在cooke的好处就是,下一次请求会直接带上上一次发放的toekn,我们在前台就不需要去做任何操作了,这样拦截器才能算上是一个独立的模块。

我们需要访问这个接口,保证同时点击的时候只能有一次

 /// <summary>
    /// 接口控制器
    /// </summary>
    public partial class homecontroller : controller
    {
        /// <summary>
        /// 接受提交请求
        /// </summary>
        /// <returns></returns>
        public jsonresult submit()
        {
            responsejson responsejson = new responsejson();
            responsejson.msg = "更新了token";
            return json(responsejson);
        }
    }

下面就是拦截器的代码

/// <summary>
    /// 适合全局的
    /// </summary>
    public class signfilter : actionfilterattribute
    {
        private redishelp cache = new redishelp();
        /// <summary>
        /// 请求之前
        /// </summary>
        /// <param name="context"></param>
        public override void onactionexecuting(actionexecutingcontext context)
        {
            // 判断是否检查登陆
            var noneedcheck = false;
            if (context.actiondescriptor is controlleractiondescriptor controlleractiondescriptor)
            {
                noneedcheck = controlleractiondescriptor.methodinfo.getcustomattributes(inherit: true)
                  .any(a => a.gettype().equals(typeof(nosignattribute)));
            }
            if (noneedcheck)return;

            responsejson responsejson = new responsejson();
            var token = context.httpcontext.request.cookies["token"];
            #region 判断数据有效性
            if (string.isnullorwhitespace(token))
            {
                responsejson.msg = "toekn不能空";
                context.result = new jsonresult(responsejson);
                return;
            } else if (cache.getvalue("token") ==null) {
                responsejson.msg = "toekn不能空";
                context.result = new jsonresult(responsejson);
                return;
            } else if (!cache.deletekey("token")) {
                responsejson.msg = "token已不存在";
                context.result = new jsonresult(responsejson);
                return;
            }
            #endregion
            //随机值
            string redistoken = guid.newguid().tostring() + new random().next(100000, 99999999);
            context.httpcontext.response.cookies.append("token",redistoken);
            //初使化并设置cookie的名称
            cache.setvalue("token", redistoken);
            base.onactionexecuting(context);
        }

        /// <summary>
        /// 请求之后
        /// </summary>
        /// <param name="context"></param>
        public override void onactionexecuted(actionexecutedcontext context)
        {

        }
    }

    /// <summary>
    /// 不需要登陆的地方加个特性
    /// </summary>
    public class nosignattribute : actionfilterattribute { }

 

 

 

 

 

 

但是这个只适合于个人在点击的时候的,下次我分享一下多人的模式。