-
業務場景介紹
在我們開發中不管是web 還是給別人的api 中一旦涉及到事務操作 。 比如添加、修改等等。一旦重複提交後造成數據錯誤。後果可想而知。目前常用的解決方案有大致兩個方向:web端防止重複提交和服務端防止重複提交。具體方案有按鈕不可點擊、彈框、服務端token。今天要記錄就是通過註解方式實現token 從而實現防止表單重複提交。
-
思路介紹
首先在調用我們需要加防止重複提交的方法前,調用我們的生成token 接口,接口返回生成唯一token ,比如時間戳或是UUID都可以。在服務端保存比如Redis。web 端也保存該token。在執行的時候帶上該參數在請求頭。通過註解和redis 保存的比較。redis 以set形式保存同時有有效期。在有效期內比較是否存在。同時比較加上同步關鍵字,防止併發。存在的話刪除redis中的token。放行執行後面的方法。 -
如何自定義防止重複提交的註解
1、定義註解
package com.api.annotation;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
/**
*
* @ClassName: ValidateRepeatableRequest
* @Description:TODO(防止重複提交註解)
* @author: drj
* @date: 2018年11月29日 下午11:14:09
*
* @Copyright: 2018
*
*/
@Documented
@Retention(RUNTIME)
@Target({ TYPE, METHOD })///接口、類、枚舉、註解、方法
public @interface ValidateRepeatableRequest {
}
2、實現註解類
package com.api.aspect;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.api.util.HttpContextUtils;
/**
*
* @ClassName: ValidateRepeatableRequestAspect
* @Description:TODO(註解類)
* @author: drj
* @date: 2018年11月30日 下午10:20:01
*
* @Copyright: 2018
*
*/
@Component
@Aspect
public class ValidateRepeatableRequestAspect {
private static final Logger logger = LoggerFactory.getLogger(ValidateRepeatableRequestAspect.class);
@Autowired
private RedisUtil jedis;
private String redisHashKey = "token";
@Pointcut("@annotation(com.api.annotation.ValidateRepeatableRequest)")
public void validateRepeatableRequest() {
}
/**
* 方法前執行
* @param joinPoint
* @throws Throwable
*/
@Before("validateRepeatableRequest()")
public void before(JoinPoint joinPoint) throws Throwable {
// 獲取http請求對象
HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
String busikey = request.getParameter("token");
if (StringUtils.isBlank(busikey)) {
JSONObject json = getParams(joinPoint);
busikey = (String) json.get("token");
}
if (StringUtils.isBlank(busikey)) {
throw new Exception("當前請求不合法,請刷新後重試!");
}
synchronized (busikey) {
Long delCount = jedis.hdel(redisHashKey, busikey);
if (delCount == null || delCount != 1) {
logger.info("接口重複提交,或者頁面長時間不操作導致token失效!");
throw new RuntimeException("當前頁面數據已更新,請刷新後重試!");
}
}
}
/**
* 獲取json參數
*
* @param joinPoint
* @return
*/
private JSONObject getParams(JoinPoint joinPoint) {
// 獲取參數值
Object[] args = joinPoint.getArgs();
if (args == null) {
return null;
}
JSONObject params = new JSONObject();
// 對象接收參數
try {
String data = JSON.toJSONString(joinPoint.getArgs()[0]);
params = JSON.parseObject(data);
}
// 普通參數傳入
catch (JSONException e) {
// 獲取參數名
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
for (int i = 0; i < methodSignature.getParameterNames().length; i++) {
params.put(methodSignature.getParameterNames()[i], args[i]);
}
}
return params;
}
}
package com.api.util;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
/**
*
* @ClassName: HttpContextUtils
* @Description:TODO(http工具類)
* @author: drj
* @date: 2018年11月29日 下午11:45:06
*
* @Copyright: 2018
*
*/
public class HttpContextUtils {
public static HttpServletRequest getHttpServletRequest() {
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
}
public static String getDomain(){
HttpServletRequest request = getHttpServletRequest();
StringBuffer url = request.getRequestURL();
return url.delete(url.length() - request.getRequestURI().length(), url.length()).toString();
}
public static String getOrigin(){
HttpServletRequest request = getHttpServletRequest();
return request.getHeader("Origin");
}
}
- 如何使用自定義註解
@RequestMapping("/getMyToken")
@ResponseBody
@ValidateRepeatableRequest
public String getMyToken(HttpServletRequest request, HttpServletResponse response) {
String token = jedis.get("token");
if (token != null && !token.equals("")) {
return token;
} else {
jedis.setex("token", 1 * 60 * 10, ToolsUtil.GetGUID());
return jedis.get("token");
}
}