Token機制,防止頁面重復提交
業(yè)務要求:頁面的數(shù)據(jù)只能被點擊提交一次
發(fā)生原因:由于重復點擊或者網(wǎng)絡重發(fā)颂翼,或者 nginx 重發(fā)等情況會導致數(shù)據(jù)被重復提交
解決辦法:
集群環(huán)境:采用 token 加 redis(redis 單線程的,處理需要排隊)
單 JVM 環(huán)境:采用 token 加 redis 或 token 加 jvm 內(nèi)存
處理流程:
數(shù)據(jù)提交前要向服務的申請 token夺克,token 放到 redis 或 jvm 內(nèi)存,token 有效時間
提交后后臺校驗 token提针,同時刪除 token攒暇,生成新的 token 返回
token 特點:要申請,一次有效性锦爵,可以限流
基于Token方式防止API接口冪等
客戶端每次在調(diào)用接口的時候,需要在請求頭中舱殿,傳遞令牌參數(shù),每次令牌只能用一次险掀。
一旦使用之后沪袭,就會被刪除,這樣可以有效防止重復提交樟氢。
步驟:
1.生成令牌接口
- 接口中獲取令牌驗證
生成令牌接口
RedisTokenUtils工具類
public class RedisTokenUtils {
private long timeout = 60 * 60;
@Autowired
private BaseRedisService baseRedisService;
// 將token存入在redis
public String getToken() {
String token = "token" + System.currentTimeMillis();
baseRedisService.setString(token, token, timeout);
return token;
}
public boolean findToken(String tokenKey) {
String token = (String) baseRedisService.getString(tokenKey);
if (StringUtils.isEmpty(token)) {
return false;
}
// token 獲取成功后 刪除對應tokenMapstoken
baseRedisService.delKey(token);
return true;
}
}
@Target(value = ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExtApiIdempotent {
String value();
}
@Target(value = ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExtApiToken {
}
自定義Api冪等注解和切面
@Component
public class ExtApiAopIdempotent {
@Autowired
private RedisTokenUtils redisTokenUtils;
@Pointcut("execution(public * com.itmayiedu.controller.*.*(..))")
public void rlAop() {
}
// 前置通知轉(zhuǎn)發(fā)Token參數(shù)
@Before("rlAop()")
public void before(JoinPoint point) {
MethodSignature signature = (MethodSignature) point.getSignature();
ExtApiToken extApiToken = signature.getMethod().getDeclaredAnnotation(ExtApiToken.class);
if (extApiToken != null) {
extApiToken();
}
}
// 環(huán)繞通知驗證參數(shù)
@Around("rlAop()")
public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
ExtApiIdempotent extApiIdempotent = signature.getMethod().getDeclaredAnnotation(ExtApiIdempotent.class);
if (extApiIdempotent != null) {
return extApiIdempotent(proceedingJoinPoint, signature);
}
// 放行
Object proceed = proceedingJoinPoint.proceed();
return proceed;
}
// 驗證Token
public Object extApiIdempotent(ProceedingJoinPoint proceedingJoinPoint, MethodSignature signature)
throws Throwable {
ExtApiIdempotent extApiIdempotent = signature.getMethod().getDeclaredAnnotation(ExtApiIdempotent.class);
if (extApiIdempotent == null) {
// 直接執(zhí)行程序
Object proceed = proceedingJoinPoint.proceed();
return proceed;
}
// 代碼步驟:
// 1.獲取令牌 存放在請求頭中
HttpServletRequest request = getRequest();
String valueType = extApiIdempotent.value();
if (StringUtils.isEmpty(valueType)) {
response("參數(shù)錯誤!");
return null;
}
String token = null;
if (valueType.equals(ConstantUtils.EXTAPIHEAD)) {
token = request.getHeader("token");
} else {
token = request.getParameter("token");
}
// 2.判斷令牌是否在緩存中有對應的令牌
// 3.如何緩存沒有該令牌的話冈绊,直接報錯(請勿重復提交)
// 4.如何緩存有該令牌的話侠鳄,直接執(zhí)行該業(yè)務邏輯
// 5.執(zhí)行完業(yè)務邏輯之后,直接刪除該令牌死宣。
if (StringUtils.isEmpty(token)) {
response("參數(shù)錯誤!");
return null;
}
if (!redisTokenUtils.findToken(token)) {
response("請勿重復提交!");
return null;
}
Object proceed = proceedingJoinPoint.proceed();
return proceed;
}
public void extApiToken() {
String token = redisTokenUtils.getToken();
getRequest().setAttribute("token", token);
}
public HttpServletRequest getRequest() {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
return request;
}
public void response(String msg) throws IOException {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletResponse response = attributes.getResponse();
response.setHeader("Content-type", "text/html;charset=UTF-8");
PrintWriter writer = response.getWriter();
try {
writer.println(msg);
} catch (Exception e) {
} finally {
writer.close();
}
}
}
頁面防止重復提交
public class OrderPageController {
@Autowired
private OrderMapper orderMapper;
@RequestMapping("/indexPage")
@ExtApiToken
public String indexPage(HttpServletRequest req) {
return "indexPage";
}
@RequestMapping("/addOrderPage")
@ExtApiIdempotent(value = ConstantUtils.EXTAPIFROM)
public String addOrder(OrderEntity orderEntity) {
int addOrder = orderMapper.addOrder(orderEntity);
return addOrder > 0 ? "success" : "fail";
}
}