欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

redis分布式锁解决表单重复提交的问题

程序员文章站 2022-03-09 10:48:55
假如用户的网速慢,用户点击提交按钮,却因为网速慢,而没有跳转到新的页面,这时的用户会再次点击提交按钮,举个例子:用户点击订单页面,当点击提交按钮的时候,也许因为网速的原因,没有跳转到新的页面,这时的用...

假如用户的网速慢,用户点击提交按钮,却因为网速慢,而没有跳转到新的页面,这时的用户会再次点击提交按钮,举个例子:用户点击订单页面,当点击提交按钮的时候,也许因为网速的原因,没有跳转到新的页面,这时的用户会再次点击提交按钮,如果没有经过处理的话,这时用户就会生成两份订单,类似于这种场景都叫重复提交。

使用redis的setnx和getset命令解决表单重复提交的问题。

1.引入redis依赖和aop依赖

		<dependency>
            <groupid>org.springframework.boot</groupid>
            <artifactid>spring-boot-starter-redis</artifactid>
            <version>1.3.8.release</version>
        </dependency>

		<dependency>
                <groupid>org.springframework.boot</groupid>
                <artifactid>spring-boot-starter-aop</artifactid>
        </dependency>

2.编写加锁和解锁的方法。

/**
 * @author wangbin
 * @description redis分布式锁
 * @date 2019年09月20日
 */
@component
public class redislock {

    private final logger logger = loggerfactory.getlogger(redislock.class);

    @autowired
    private stringredistemplate redistemplate;

    /**
     * @author wangbin
     * @description 进行加锁的操作(该方法是单线程运行的)
     * @date 2019年09月20日
     * @param key 某个方法请求url加上cookie中的用户身份使用md5加密生成
     * @param value 当前时间+过期时间(10秒)
     * @return true表示加锁成功   false表示未获取到锁
     */
    public boolean lock(string key,string value){
        //加锁成功返回true
        if(redistemplate.opsforvalue().setifabsent(key,value,10, timeunit.seconds)){
            return true;
        }
        string currentvalue = redistemplate.opsforvalue().get(key);
        //加锁失败,再判断是否由于解锁失败造成了死锁的情况
        if(stringutils.isnotempty(currentvalue) && long.parselong(currentvalue) < system.currenttimemillis()){
            //获取上一个锁的时间,并且重新设置锁
            string oldvalue = redistemplate.opsforvalue().getandset(key, value);
            if(stringutils.isnotempty(oldvalue) && oldvalue.equals(currentvalue)){
                //设置成功,重新设置锁是保证了单线程的运行
                return true;
            }
        }
        return false;
    }

    /**
     * @author wangbin
     * @description 进行解锁的操作
     * @date 2019年09月20日
     * @param key 某个方法请求url使用md5加密生成
     * @param value 当前时间+过期时间
     * @return
     */
    public void unlock(string key,string value){
        try {
            string currentvalue = redistemplate.opsforvalue().get(key);
            if(stringutils.isnotempty(currentvalue) && currentvalue.equals(value)){
                redistemplate.delete(key);
            }
        }catch (exception e){
            logger.error("redis分布式锁,解锁异常",e);
        }
    }


    /**
     * @author wangbin
     * @description 进行解锁的操作
     * @date 2019年09月20日
     * @param key 某个方法请求url使用md5加密生成
     * @return
     */
    public void unlock(string key){
        try {
            string currentvalue = redistemplate.opsforvalue().get(key);
            if(stringutils.isnotempty(currentvalue)){
                redistemplate.delete(key);
            }
        }catch (exception e){
            logger.error("redis分布式锁,解锁异常",e);
        }
    }
}

3.使用拦截器在请求之前进行加锁的判断。

@configuration
public class logininterceptor extends handlerinterceptoradapter {
    private final logger logger = loggerfactory.getlogger(logininterceptor.class);
    //超时时间设置为10秒
    private static final int timeout = 10000;
    @autowired
    private stringredistemplate stringredistemplate;
    @autowired
    private redislock redislock;
    /**
     * 在请求处理之前进行调用(controller方法调用之前)
     * 基于url实现的拦截器
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws exception
     */
    @override
    public boolean prehandle(httpservletrequest request, httpservletresponse response, object handler) throws exception {
        string path = request.getservletpath();
        if (path.matches(constants.no_interceptor_path)) {
            //不需要的拦截直接过
            return true;
        } else {
            // 这写你拦截需要干的事儿,比如取缓存,session,权限判断等
            //判断是否是重复提交的请求
			if(!redislock.lock(digestutils.md5hex(request.getrequesturi()+value),string.valueof(system.currenttimemillis()+timeout))){
                        logger.info("===========获取锁失败,该请求为重复提交请求");
                        return false;
 			 }
            return true;
        }
    }
}

4.使用aop在后置通知中进行解锁。

/**
 * @author wangbin
 * @description 使用redis分布式锁解决表单重复提交的问题
 * @date 2019年09月20日
 */
@aspect
@component
public class repeatedsubmit {

    @autowired
    private redislock redislock;

    //定义切点
    @pointcut("execution(public * com.kunluntop.logistics.controller..*.*(..))")
    public void pointcut(){

    }

    //在方法执行完成后释放锁
    @after("pointcut()")
    public void after(){
        servletrequestattributes attributes = (servletrequestattributes) requestcontextholder.getrequestattributes();
        httpservletrequest request = attributes.getrequest();
        redislock.unlock(digestutils.md5hex(request.getrequesturi()+ cookieutils.getcookie(request,"userkey")));
    }
}

到此这篇关于redis分布式锁解决表单重复提交的问题的文章就介绍到这了,更多相关redis 表单重复提交内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!