1. 背景介绍
在开发项目中肯可能会出现如下情况:
1. 用户的失误操作,多次点击表单提交按钮
2. 由于网速等原因造成页面卡顿,用户重复刷新提交页面
3. 黑客或恶意用户使用postman等工具重复恶意提交表单
....
这些情况都会导致表单的重复提交,导致数据重复,增加服务器的压力,甚至会造成服务器宕机,因此要有效防止表单重复提交非常必要。
2. 解决方案
2.1 点击一次之后,按钮失效(不推荐,用户刷新页面仍能重复提交)
通过js代码,当用户点击提交按钮后,屏蔽提交按钮(使按钮无发点击提交或点击无效disabled),从而实现防止表单重复提交。
2.2 用redirect来解决重复提交问题
简而言之,表单提交后重定向到提交成功的一个页面。
2.3在数据库里添加约束
在数据库里添加唯一约束或创建唯一索引,防止出现重复数据。简单粗暴的方法。
2.4 在session中存放一个特殊标志(推荐)。
类似于“令牌”机制。当表单页面第一次被请求时,生成一个特殊的字符标志串,存在session中,同时放在表单的隐藏域里。接受处理表单数据时,检查标识字串是否存在,若标志串相同则处理表单提交并立即从session中删除它。若不一致就是重复提交了则忽略这次提交。如果发现表单提交里没有有效的标志串,这说明表单已经被提交过了,忽略这次提交。
(解决struts2重复提交,可以结合s:token标签解决重复提交问题)
2.5 使用AOP自定义切入实现
实现原理:
- 自定义防止重复提交标记(@AvoidRepeatableCommit)。
- 对需要防止重复提交的Congtroller里的mapping方法加上该注解。
- 新增Aspect切入点,为@AvoidRepeatableCommit加入切入点。
- 每次提交表单时,Aspect都会保存当前key到reids(须设置过期时间)。
- 重复提交时Aspect会判断当前redis是否有该key,若有则拦截。
自定义标签
import java.lang.annotation.*;
/**
* 避免重复提交
* @author dsx
* @version
* @since
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AvoidRepeatableCommit {
/**
* 指定时间内不可重复提交,单位毫秒
* @return
*/
long timeout() default 30000 ;
}
自定义切面Aspect
/**
* 解决重复提交aop
* @author dsx
* @version
* @since
*/
@Aspect
@Component
public class AvoidRepeatableCommitAspect {
@Autowired
private RedisTemplate redisTemplate;
/**
* @param point
*/
@Around("@annotation(com.xwolf.boot.annotation.AvoidRepeatableCommit)")
public Object around(ProceedingJoinPoint point) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest();
String ip = IPUtil.getIP(request);
//获取注解
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
//目标类、方法
String className = method.getDeclaringClass().getName();
String name = method.getName();
String ipKey = String.format("%s#%s",className,name);
int hashCode = Math.abs(ipKey.hashCode());
String key = String.format("%s_%d",ip,hashCode);
log.info("ipKey={},hashCode={},key={}",ipKey,hashCode,key);
AvoidRepeatableCommit avoidRepeatableCommit = method.getAnnotation(AvoidRepeatableCommit.class);
long timeout = avoidRepeatableCommit.timeout();
if (timeout < 0){
//过期时间5分钟
timeout = 60*5;
}
String value = (String) redisTemplate.opsForValue().get(key);
if (StringUtils.isNotBlank(value)){
return "请勿重复提交";
}
redisTemplate.opsForValue().set(key, UUIDUtil.uuid(),timeout,TimeUnit.MILLISECONDS);
//执行方法
Object object = point.proceed();
return object;
}
}
如有不足,望不吝赐教!!!