使用Redission实现接口防重复提交

一、定义注解 Idempotent

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

/**
 * @Author: 
 * @Description: 幂等注解 主要作用于方法和类上 作用在类上表示这个类里所有的方法都做限制
 * 如果要使用nacos配置文件,不要这里使用@ConfigurationProperties,而是应该再写一个类
 * 使用注解获取ncos的配置后,在切面里覆盖这个元数据
 * @Date: 2024/4/25
 **/
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {

    /**
     * 幂等的超时时间,默认为 1 秒
     *
     * 注意,如果执行时间超过它,请求还是会进来
     */
    int timeout() default 3;

    /**
     * 时间单位,默认为 SECONDS 秒
     */
    TimeUnit timeUnit() default TimeUnit.SECONDS;

    /**
     * redis锁前缀
     * @return
     */
    String keyPrefix() default "submit:";

    /**
     * key分隔符
     * @return
     */
    String delimiter() default ":";

    /**
     * 提示信息,正在执行中的提示
     */
    String message() default "重复请求,请稍后重试";
}

二、定义切面

import lombok.SneakyThrows;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.StringUtils;

@Aspect
@Configuration
public class IdempotentAspect {

    @Autowired
    private RedissonClient redissonClient;


    @SneakyThrows
    @Around(value = "@within(Idempotent) || @annotation(Idempotent)")
    public Object interceptor(ProceedingJoinPoint joinPoint) {
        // 先获取类上的,如果没有再获取方法上的
        Class<?> clazz = joinPoint.getTarget().getClass();
        Idempotent idempotent = AnnotationUtils.findAnnotation(clazz, Idempotent.class);
         if (ObjectUtil.isNull(idempotent)){
            //获取连接点的方法签名对象
            MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
            //Method对象
            Method method = methodSignature.getMethod();
            String name = methodSignature.getReturnType().getName();
            //获取Method对象上的注解对象
            idempotent = method.getAnnotation(Idempotent.class);
        }
        assert idempotent != null;
        if (StringUtils.isEmpty(idempotent.keyPrefix())) {
            throw new RuntimeException("重复提交前缀不能为空");
        }
        String userId = "1132";
        //获取自定义key
        final String lockKey = getLockKey(joinPoint,userId,idempotent );
        // 使用Redisson分布式锁的方式判断是否重复提交
        RLock lock = redissonClient.getLock(lockKey);
        boolean isLocked = false;
        try {
            //尝试抢占锁 在多个服务之间共享状态,所以适用于分布式环境
            // 本身在获得锁和设置锁时就是原子操作,所以不需要额外使用同步方法
            isLocked = lock.tryLock();
            //没有拿到锁说明已经有了请求了
            if (!isLocked) {
                throw new RuntimeException(idempotent.message());
            }
            //拿到锁后设置过期时间
            lock.lock(idempotent.timeout(), idempotent.timeUnit());
            return joinPoint.proceed();
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        } finally {
            //释放锁
            if (isLocked && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    @SneakyThrows
    @Before(value = "@within(Idempotent) || @annotation(Idempotent)")
    public void interceptorBefpre(JoinPoint joinPoint) {
        System.out.println("前置处理,注意参数JoinPoint和环绕ProceedingJoinPoint不一样");
    }
    /**
     * 获取LockKey
     *
     * @param joinPoint 切入点
     * @return
     */
    public static String getLockKey(ProceedingJoinPoint joinPoint, String userId,Idempotent idempotent) {
       // signature 是签名的意思,可以获取到当前请求所对应的方法名
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();

        Object[] args = joinPoint.getArgs();
        StringBuilder sb = new StringBuilder(userId);
        sb.append(idempotent.delimiter()).append(name);

        if (args.length > 0) {
            for (Object arg : args) {
                Class<?> argClass = arg.getClass();
                Field[] fields = argClass.getDeclaredFields();
                for (Field field : fields) {
                    if (field.isAnnotationPresent(RequestKeyParam.class)) {
                        field.setAccessible(true); // 允许访问私有字段
                        // 字段上有@RequestKeyParam注解,获取注解的内部属性 暂时没用到
                        RequestKeyParam requestKeyParam = field.getAnnotation(RequestKeyParam.class);
                        String description = requestKeyParam.description();
                        // 获取字段值
                        Object fieldValue = field.get(arg);
                        System.out.println("Field name: " + field.getName() + ", Value: " + fieldValue);
                        // 这里你可以根据需要对fieldValue进行处理
                        sb.append(idempotent.delimiter()).append(fieldValue);
                    }
                }
            }
        }
        //返回指定前缀的key "submit:1132:query:123" 如果是get请求则会是submit:1132:query 没有参数作为key
        return idempotent.keyPrefix() + sb;
    }

三、使用注解

@RestController
@RequestMapping
@Idempotent
public class testController {
    /**
     * 测试 防重复提交
     * @param reqVO
     * @return
     */
    @PostMapping("/add")
    public Result queryScanCodeSwitch(@RequestBody ProjectReqVO reqVO) {
        return Result.ok(reqVO);
    }

    /**
     * 测试 防重复提交
     * @param reqVO
     * @return
     */
    @PostMapping("/delete")
    public Result delete(@RequestBody ProjectReqVO reqVO) {
        return Result.ok(reqVO);
    }
}

四、测试类

import lombok.Data;

/**
 * 项目管理 新增 VO
 *
 */
@Data
public class ProjectReqVO {

    /**
     * 合同编号
     */
    private String contractNo;

    /**
     * 项目名字
     */
    private String name;

    /**
     * 项目状态
     */
    private Integer status;

}

参考的这位大哥写的,并做了一点简化SpringBoot接口防抖(防重复提交),接口幂等性,轻松搞定_springboot 防抖重复提交-CSDN博客

  • 7
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
Redission是一个基于Java的分布式锁实现库。它提供了简单易用的API,可以帮助开发人员在分布式环境中实现锁功能。要使用Redission实现分布式锁,你可以按照以下步骤进行操作: 1. 首先,你需要在你的Java项目中添加Redission的依赖。可以使用Maven或Gradle构建工具来添加依赖项。 2. 创建一个Redission客户端实例。你可以使用Redission提供的`Config`对象来配置客户端连接到Redis服务器。例如,可以指定Redis服务器的主机名、端口号等信息。 3. 通过调用`RedissionClient`的`getLock`方法来获取一个分布式锁对象。可以使用不同的名称创建多个锁对象。 4. 通过调用锁对象的`lock`方法来获取锁。这会阻塞当前线程,直到成功获取到锁为止。你也可以使用`tryLock`方法来尝试获取锁,如果获取失败,则立即返回。 5. 执行需要保护的临界区代码。 6. 在代码执行完毕后,通过调用锁对象的`unlock`方法来释放锁,以便其他线程可以获取到锁。 下面是一个简单示例代码,演示了如何使用Redission实现分布式锁: ```java import org.redisson.Redisson; import org.redisson.api.RedissonClient; import org.redisson.config.Config; import org.redisson.api.RLock; public class DistributedLockExample { public static void main(String[] args) { // 创建Redission客户端实例 Config config = new Config(); config.useSingleServer().setAddress("redis://localhost:6379"); RedissonClient client = Redisson.create(config); // 获取分布式锁对象 RLock lock = client.getLock("myLock"); try { // 获取锁 lock.lock(); // 执行需要保护的临界区代码 System.out.println("执行临界区代码"); } finally { // 释放锁 lock.unlock(); } // 关闭Redission客户端实例 client.shutdown(); } } ``` 以上就是使用Redission实现分布式锁的基本步骤。通过Redission,你可以方便地在分布式环境中实现锁功能,确保多个进程或线程之间的数据一致性和安全性。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

刘个Java

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值