假如用户的网速慢,用户点击提交按钮,却因为网速慢,而没有跳转到新的页面,这时的用户会再次点击提交按钮,举个例子:用户点击订单页面,当点击提交按钮的时候,也许因为网速的原因,没有跳转到新的页面,这时的用户会再次点击提交按钮,如果没有经过处理的话,这时用户就会生成两份订单,类似于这种场景都叫重复提交。
使用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 表单重复提交内容请搜索www.887551.com以前的文章或继续浏览下面的相关文章希望大家以后多多支持www.887551.com!