栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

自定义spring-boot-starter 实现 幂等注解 防止重复提交

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

自定义spring-boot-starter 实现 幂等注解 防止重复提交

一般遇见这种需求,大体思路思路我想基本是这样的,

1.自定义一个spring-boot-starter

2.启动一个拦截器实现拦截自定义注解

3.根据注解的一些属性进行拼接一个key

4.判断key是否存在

4.1 不存在 存入redis,然后设置一个过期时间(一般过期时间也是注解的一个属性)

4.2 存在则抛出一个重复提交异常 

闲话少说,先来一个使用端代码以及结果

使用方式

key = "T(cn.goswan.orient.common.security.util.SecurityUtils).getUser().getUsername()+#test.id"

这部分 的key就是拦截器里面用到的判断的key,具体可以根据自己业务用el表达式去定义

我用的是class fullpanth+用户名+业务主键 当作判定key

expireTime = 3

设置为了 3 

timeUnit = TimeUnit.SECONDS

设置为了秒,即为3秒后这个key从缓存中消失,使用端一定注意这个时常一定要大于自己的业务处理耗时

好了下面上结果,连续发送两次请求(postman 发送)第一次请求并没有报错

第二次请求抛出如下错误(自定义的错误)

exception.IdempotentException: classUrl public cn.goswan.orient.common.core.util.R com..demo.controller.TestController.save(com.demo.entity.Test) not allow repeat submit 

好了,说了这么多,下面上源码


目录结构


pom 文件(这里的comm-data实际上内部是对redis 的引用配置可以忽略,大家可以替换成自己的redis 配置即可,如果有不明白的可以看看我之前的文件,redis templete 哨兵配置代码参考一下)



    
        cn.goswan
        orient-common
        3.9.0
    
    4.0.0
    basal-common-idempotent
    
        
            org.redisson
            redisson-spring-boot-starter
        
        
            cn.goswan
            orient-common-data
        
    


Idempotent.java
package com.basal.common.idempotent.annotation;

import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;



@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {

    
    String key() default "";


//    
//    boolean isWorkonAll() default false;

    
    int expireTime() default 1;

    
    TimeUnit timeUnit() default TimeUnit.SECONDS;
}

IdempotentAspect.java
package com.basal.common.idempotent.aspect;

import cn.goswan.orient.common.data.util.StringUtils;
import com.basal.common.idempotent.annotation.Idempotent;
import com.basal.common.idempotent.exception.IdempotentException;
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.redisson.Redisson;
import org.redisson.api.RMapCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.spel.standard.Spelexpression;
import org.springframework.expression.spel.standard.SpelexpressionParser;
import org.springframework.expression.spel.support.StandardevaluationContext;
import java.lang.reflect.Method;
import java.util.Objects;


@Aspect
public class IdempotentAspect {

    final SpelexpressionParser PARSER = new SpelexpressionParser();

    final LocalVariableTableParameterNameDiscoverer DISCOVERER = new LocalVariableTableParameterNameDiscoverer();

    private static final String RMAPCACHE_KEY = "idempotent";

    @Autowired
    private Redisson redisson;

    @Pointcut("@annotation(com.basal.common.idempotent.annotation.Idempotent)")
    public void pointCut() {
    }

    @Before("pointCut()")
    public void beforeCut(JoinPoint joinPoint) {


        //获取切面拦截的方法
        Object[] arguments = joinPoint.getArgs();
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        if (!methodSignature.getMethod().isAnnotationPresent(Idempotent.class)) {
            return;
        }
        Method method = ((MethodSignature) signature).getMethod();
        if (method.getDeclaringClass().isInterface()) {
            try {
                method = joinPoint.getTarget().getClass().getDeclaredMethod(joinPoint.getSignature().getName(),
                        method.getParameterTypes());
            } catch (SecurityException | NoSuchMethodException e) {
                throw new RuntimeException(e);
            }
        }

        //获取切面拦截的方法的参数并放入值context中
        StandardevaluationContext context = new StandardevaluationContext();
        String[] params = DISCOVERER.getParameterNames(method);
        if (params != null && params.length > 0) {
            for (int len = 0; len < params.length; len++) {
                context.setVariable(params[len], arguments[len]);
            }
        }

        //获取类全路径作为根key
        String classUrl = method.toString();
        Idempotent idempotent = methodSignature.getMethod().getAnnotation(Idempotent.class);
        String idKey = "";
        if (StringUtils.isEmpty(idempotent.key())) {
            idKey = classUrl;
        } else {

            //将annotation中的key 获取到并通过spelexpression 转为具体值
            Spelexpression spelexpression = PARSER.parseRaw(idempotent.key());
            String key = spelexpression.getValue(context, String.class);
            idKey = classUrl + key;
        }

        //判断map 中是否已经存在key
        RMapCache rMapCache = redisson.getMapCache(RMAPCACHE_KEY);

        //存在则抛出重复提交异常
        if (rMapCache.containsKey(idKey)) {

            throw new IdempotentException("classUrl " + classUrl + " not allow repeat submit ");
        } else {

            //不存在则存入cache map,如果存入过程中又有操作以至于存在key,则同样抛出异常
            Object idObj = rMapCache.putIfAbsent(idKey, System.currentTimeMillis(), idempotent.expireTime(), idempotent.timeUnit());
            if (Objects.nonNull(idObj)) {
                throw new IdempotentException("classUrl " + classUrl  + " not allow repeat submit ");
            }
        }


    }
}

IdempotentConfig.java
package com.basal.common.idempotent.config;

import com.basal.common.idempotent.aspect.IdempotentAspect;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class IdempotentConfig {


    @Bean
    public IdempotentAspect IdempotentAspect(){
        IdempotentAspect idempotentAspect = new IdempotentAspect();
        return idempotentAspect;
    }
}
IdempotentException.java
package com.basal.common.idempotent.exception;


public class IdempotentException extends RuntimeException {

    public IdempotentException() {
        super();
    }

    public IdempotentException(String message) {
        super(message);
    }

    public IdempotentException(String message, Throwable cause) {
        super(message, cause);
    }

    public IdempotentException(Throwable cause) {
        super(cause);
    }

    protected IdempotentException(String message, Throwable cause, boolean enableSuppression,
                                  boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }

}

spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=
  com.basal.common.idempotent.config.IdempotentConfig

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/697687.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号