後端--防重複提交策略方法
阿新 • • 發佈:2021-01-01
後端–防重複提交策略方法
原因:
前臺操作的抖動,快速操作,網路通訊或者後端響應慢,都會增加後端重複處理的概率。
情形
- 由於使用者誤操作,多次點選表單提交按鈕。
- 由於網速等原因造成頁面卡頓,使用者重複重新整理提交頁面。
- 黑客或惡意使用者使用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;
}
}