zoukankan      html  css  js  c++  java
  • 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'

     这样算是完成了,再次出现二次请求时直接会被拦截。

  • 相关阅读:
    SpringMVC项目模块浅析
    利用python脚本(xpath)抓取数据
    利用python脚本(re)抓取美空mm图片
    Intellij idea开发Hadoop MapReduce程序
    在Hadoop平台跑python脚本
    Hadoop常用命令
    CentOS6.5 安装Zookeeper集群
    MySQL常用命令
    MongoDB常用命令
    前后端分离架构+k8s+ingress
  • 原文地址:https://www.cnblogs.com/hegeainiyo/p/13600143.html
Copyright © 2011-2022 走看看