使用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