Java使用@Idempotent注解處理冪等問題,防止二次點擊


Java使用自定義注解@Idempotent處理冪等問題,防止二次點擊

冪等實現原理就是利用AOP面向切面編程,在執行業務邏輯之前插入一個方法,生成一個token,存入redis並插入到response中返回給前台,

然后前台再拿着這個token發起請求,經過判斷,只執行第一次請求,多余點擊的請求都攔截下來.

創建自定義注解@Idempotent

package org.jeecg.common.annotation;

import java.lang.annotation.*;

//注解信息會被添加到Java文檔中
@Documented
//注解的生命周期,表示注解會被保留到什么階段,可以選擇編譯階段、類加載階段,或運行階段
@Retention(RetentionPolicy.RUNTIME)
//注解作用的位置,ElementType.METHOD表示該注解僅能作用於方法上
@Target(ElementType.METHOD)
public @interface Idempotent {
}

創建自定義注解@IdempotentToken

 

package org.jeecg.common.annotation;

import java.lang.annotation.*;

//注解信息會被添加到Java文檔中
@Documented
//注解的生命周期,表示注解會被保留到什么階段,可以選擇編譯階段、類加載階段,或運行階段
@Retention(RetentionPolicy.RUNTIME)
//注解作用的位置,ElementType.METHOD表示該注解僅能作用於方法上
@Target(ElementType.METHOD)
public @interface IdempotentToken {
}

 

@Idempotent注解的配置類 IdempotentInterceptor

 

 

package org.jeecg.config.idempotent;

import cn.hutool.core.util.ObjectUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.jeecg.common.annotation.Idempotent; import org.jeecg.common.annotation.IdempotentToken; import org.jeecg.common.util.RedisUtil; import org.springframework.stereotype.Component; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.lang.reflect.Method; import java.util.UUID; /** * @author zbw */ @Slf4j @Component public class IdempotentInterceptor implements HandlerInterceptor { private static final String VERSION_NAME = "version"; private static final String TOKEN_NAME = "idempotent_token"; private static final String IDEMPOTENT_TOKEN_PREFIX = "idempotent:token:"; /*private RedisTemplate<String, Object> redisTemplate;*/ private RedisUtil redisUtil; public IdempotentInterceptor(RedisUtil redisUtil){ this.redisUtil = redisUtil; } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (!(handler instanceof HandlerMethod)) { return true; } HandlerMethod handlerMethod = (HandlerMethod) handler; Method method = handlerMethod.getMethod(); IdempotentToken idempotentTokenAnnotation = method.getAnnotation(IdempotentToken.class); if(idempotentTokenAnnotation!=null){ //重新更新token String token = UUID.randomUUID().toString().replaceAll("-",""); response.addHeader(TOKEN_NAME,token); //解決后端傳遞token前端無法獲取問題 response.addHeader("Access-Control-Expose-Headers",TOKEN_NAME); redisUtil.set(IDEMPOTENT_TOKEN_PREFIX+token,token); } Idempotent idempotentAnnotation = method.getAnnotation(Idempotent.class); if(idempotentAnnotation!=null){ checkIdempotent(request); } return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } private void checkIdempotent(HttpServletRequest request) { //首先到request中去拿TOKEN_NAME String token = request.getHeader(TOKEN_NAME); if(ObjectUtil.isEmpty(token)){ token = ""; } if (StringUtils.isBlank(token)) {// header中不存在token token = request.getParameter(TOKEN_NAME); if (StringUtils.isBlank(token)) {// parameter中也不存在token throw new IllegalArgumentException("冪等token丟失,請勿重復提交"); } } if (!redisUtil.hasKey(IDEMPOTENT_TOKEN_PREFIX+token)) { throw new IllegalArgumentException("請勿重復提交"); } boolean bool = redisUtil.delete(IDEMPOTENT_TOKEN_PREFIX+token); if(!bool){ throw new IllegalArgumentException("沒有刪除對應的token"); } } }

 

 

 

這個注解配置是基於redis的,在這里redis的配置略過,本章只是講解如何簡單的使用

 

 后端的話,需要在該頁面的list查詢上加上@IdempotentToken注解,原理是涉及到查詢成功后會刷新一下頁面,重新獲取token

@Idempotent注解配置完成就可以直接加在controller的對應方法上,如圖:

 

 前端的一些配置(這里我用的是ant design vue):

user.js里面加入

 

idempotentToken:''

SET_IDEMPOTENT_TOKEN:(state,token)=>{
  state.idempotentToken = token
},


getter.js里面加入

idempotentToken: state => state.user.idempotentToken,

request.js里面修改

// request interceptor
service.interceptors.request.use(config => {
  const token = Vue.ls.get(ACCESS_TOKEN)
  if (token) {
    config.headers[ 'X-Access-Token' ] = token // 讓每個請求攜帶自定義 token 請根據實際情況自行修改
  }
  const idempotent_token = Vue.ls.get(IDEMPOTENT_TOKEN)
  if(idempotent_token){
    config.headers['idempotent_token'] = idempotent_token
  }
  //update-begin-author:taoyan date:2020707 for:多租戶
  let tenantid = Vue.ls.get(TENANT_ID)
  if (!tenantid) {
    tenantid = 0;
  }
  config.headers[ 'tenant_id' ] = tenantid
  //update-end-author:taoyan date:2020707 for:多租戶
  if(config.method=='get'){
    if(config.url.indexOf("sys/dict/getDictItems")<0){
      config.params = {
        _t: Date.parse(new Date())/1000,
        ...config.params
      }
    }
  }
  return config
},(error) => {
  return Promise.reject(error)
})

// response interceptor
service.interceptors.response.use((response) => {
  let idempotent_token = response.headers[IDEMPOTENT_TOKEN]
  if(idempotent_token){
    Vue.ls.set(IDEMPOTENT_TOKEN,idempotent_token,7 * 24 * 60 * 60 * 1000)
    store.commit('SET_IDEMPOTENT_TOKEN', idempotent_token)
  }
    return response.data
  }, err)

const installer = {
  vm: {},
  install (Vue, router = {}) {
    Vue.use(VueAxios, router, service)
  }

mutation-type.js加入定義的常量

export const IDEMPOTENT_TOKEN='idempotent_token'

 

 

 這樣算是完成了,再次出現二次請求時直接會被攔截。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM