package com.cfpay.collection.module.idempotent.aop;

import com.cfpay.collection.common.constant.ErrorCodeConstants;
import com.cfpay.collection.common.constant.RedisConstants;
import com.cfpay.collection.common.exception.GlobalException;
import com.cfpay.collection.common.util.AssertUtils;
import com.cfpay.collection.common.util.CollectionUtils;
import com.cfpay.collection.framework.redis.core.RedisCache;
import com.cfpay.collection.module.idempotent.annotation.Idempotent;
import com.cfpay.collection.module.idempotent.config.IdempotentProperties;
import com.cfpay.collection.module.idempotent.resolver.IdempotentKeyResolver;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

import java.util.List;
import java.util.Map;

/**
 * 拦截声明了 'Idempotent' 注解的方法,实现幂等操作
 *
 * @author:gaofv
 * @date:2023/7/8 14:27
 */
@Aspect
@Slf4j
public class IdempotentAspect {

    private final Map<Class<? extends IdempotentKeyResolver>, IdempotentKeyResolver> keyResolvers;
    private IdempotentProperties properties;
    private RedisCache redisCache;

    public IdempotentAspect(List<IdempotentKeyResolver> keyResolvers, RedisCache redisCache, IdempotentProperties properties) {
        this.keyResolvers = CollectionUtils.convertMap(keyResolvers, IdempotentKeyResolver::getClass);
        this.redisCache = redisCache;
        this.properties = properties;
    }

    @Before("@annotation(idempotent)")
    public void beforePointCut(JoinPoint joinPoint, Idempotent idempotent) {
        // 获得 IdempotentKeyResolver
        IdempotentKeyResolver keyResolver = keyResolvers.get(idempotent.keyResolver());
        AssertUtils.notNull(keyResolver, '找不到对应的 IdempotentKeyResolver');
        // 解析 Key
        KeyValue keyValue = keyResolver.resolver(joinPoint, idempotent);
        String redisKey = String.format(RedisConstants.IDEMPOTENT_KEY, keyValue.getKey());
        // 获取timeout和timeUnit
        Long timeout = idempotent.timeout();
        if (timeout.longValue() < 0){
            timeout = properties.getTimeout();
        }
        // 锁定 Key
        boolean success = redisCache.setIfAbsent(redisKey, keyValue.getValue(), timeout, idempotent.timeUnit());
        // 锁定失败,抛出异常
        if (!success) {
            log.info('[beforePointCut][方法({}) 参数({}) 存在重复请求]', joinPoint.getSignature().toString(), joinPoint.getArgs());
            throw new GlobalException(ErrorCodeConstants.REPEATED_REQUESTS.getCode(), idempotent.message());
        }
    }
}

这段代码是一个切面类,用于拦截带有'@Idempotent'注解的方法,实现幂等操作。在方法执行前,会先执行'@Before'注解修饰的beforePointCut方法。

在beforePointCut方法中,首先根据'@Idempotent'注解中的keyResolver属性获取对应的IdempotentKeyResolver实例,然后调用该实例的resolver方法解析方法参数,获取到幂等操作的key和value。

接着,根据解析得到的key和value,构建Redis的key,并调用redisCache的setIfAbsent方法,尝试将该key-value对存入Redis缓存中。如果存入成功,说明该请求是第一次重复的请求,可以继续执行方法;如果存入失败,说明该请求已经存在,即重复请求,将抛出GlobalException异常。

需要注意的是,如果'@Idempotent'注解的timeout属性小于0,会从配置文件中获取timeout的值。并且,存入Redis缓存的时长为timeout和timeUnit属性指定的时长。

总结来说,这段代码的作用是通过拦截带有'@Idempotent'注解的方法,在方法执行前判断该请求是否重复,实现幂等操作。

Java 幂等性处理 AOP 实现 - 使用 Redis 缓存防止重复请求

原文地址: https://www.cveoy.top/t/topic/fptQ 著作权归作者所有。请勿转载和采集!

免费AI点我,无需注册和登录