使用SpringBoot自定义注解+AOP+redisson锁来实现防接口幂等性重复提交

1 前提,整合好springboot和redis,redisson的环境

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2 编写自定义注解,注解的作用是标记一个方法是否支持防重提交

import java.lang.annotation.*;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NotRepeatDuplication {
    
    /**
     * 延时时间 在延时多久后可以再次提交,默认10秒
     * @return 秒
     */
    int delaySeconds() default 10;
    
}

3 AOP切面逻辑,来判断一个方法是否被标记了该注解。如果被标记了该注解,那么就需要对该方法进行特殊处理,以实现幂等性

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.DigestUtils;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;

/**
 * 使用Redisson分布式锁来防止接口重复调用的AOP切面类
 */
@Component
@Aspect
@Slf4j
public class LockMethodAOP {
    
    @Autowired
    private RedissonClient redissonClient;
    
    /**
     * @param pjp:                  切入点对象,包含被拦截方法的信息
     * @param notRepeatDuplication: 自定义注解,用于指定防重复提交的延迟时间
     * @throws Throwable: 抛出异常时重新抛出
     * @description: 环绕通知方法,用于在方法执行前获取分布式锁,在方法执行后释放锁
     * @return: 执行被拦截方法的返回值
     */
    @Around("execution(* com.example.controler..*.*(..))  && @annotation(notRepeatDuplication)  ")
    public Object interceptor(ProceedingJoinPoint pjp, NotRepeatDuplication notRepeatDuplication) throws Throwable {
        // 生成锁的唯一键
        String lockKey = generateKey(pjp);
        // 获取Redisson分布式锁
        RLock lock = redissonClient.getLock(lockKey);
        try {
            // 尝试获取锁,如果在指定时间内未获取到锁,则抛出异常表示重复提交
            boolean b = lock.tryLock(0, notRepeatDuplication.delaySeconds(), TimeUnit.SECONDS);
            if (!b) {
                throw new RuntimeException("请忽重复提交");
            }
            // 执行被拦截的方法
            return pjp.proceed();
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }
    
    /**
     * @param pjp: 切入点对象,包含被拦截方法的信息
     * @description: 生成锁的唯一键
     * @return: MD5加密后的字符串,用作锁的唯一键
     */
    private String generateKey(ProceedingJoinPoint pjp) {
        /**
         * 取得类名,方法名,参数
         */
        String className = pjp.getTarget().getClass().getName();
        String methodName = pjp.getSignature().getName();
        Object[] args = pjp.getArgs();
        
        StringBuilder sb = new StringBuilder();
        sb.append(className).append(":").append(methodName);
        for (Object arg : args) {
            sb.append(":").append(arg.toString());
        }
        
        log.info("锁的唯一键:{}",sb.toString());
        // 使用MD5加密生成的字符串,确保键的唯一性
        String s = DigestUtils.md5DigestAsHex(sb.toString().getBytes(StandardCharsets.UTF_8));
        log.info("锁的唯一键:{}",s);
        return s;
    }
}

4 测试,需要防重提交的方法,加上注解

@RestController
public class DemoController {
 
    @Autowired
    private TBookOrderMapper tBookOrderMapper;
 
    @GetMapping("/demo")
    @NotRepeatDuplication(delaySeconds = 10)
    public String demo(@RequestBody TBookOrder tBookOrder) {
        // 处理请求
        int insert = tBookOrderMapper.insert(tBookOrder);
        return insert==1?"ok":"wrong";
    }
}

作者:小哇666

物联沃分享整理
物联沃-IOTWORD物联网 » 使用SpringBoot自定义注解+AOP+redisson锁来实现防接口幂等性重复提交

发表回复