后端–防重复提交策略方法
原因:
前台操作的抖动,快速操作,网络通信或者后端响应慢,都会增加后端重复处理的概率。
情形
- 由于用户误操作,多次点击表单提交按钮。
- 由于网速等原因造成页面卡顿,用户重复刷新提交页面。
- 黑客或恶意用户使用postman等工具重复恶意提交表单(攻击网站)。
- 这些情况都会导致表单重复提交,造成数据重复,增加服务器负载,严重甚至会造成服务器宕机。因此有效防止表单重复提交有一定的必要性。
解决方案:
一:给数据库增加唯一键约束
这种方法需要在代码中加入捕捉插入数据异常:
try {
// insert
} catch (DuplicateKeyException e) {
logger.error("user already exist");
}
二:使用AOP自定义切入实现
实现原理:
1.自定义防止重复提交标记(@AvoidRepeatableCommit)。
2.对需要防止重复提交的Congtroller里的mapping方法加上该注解。
3.新增Aspect切入点,为@AvoidRepeatableCommit加入切入点。
4.每次提交表单时,Aspect都会保存当前key到reids(须设置过期时间)。
5.重复提交时Aspect会判断当前redis是否有该key,若有则拦截。
/**
* 避免重复提交
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AvoidRepeatableCommit {
/**
* 指定时间内不可重复提交,单位秒
* @return
*/
long timeout() default 3 ;
}
/**
* 重复提交aop
*/
@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;
}
}