使用Redis分布式锁保证接口幂等性(AOP注解)

注解定义

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

/**
 * 防止重复提交注解
 * @author
 */
@Target(value = {ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RejectRepeatedSubmit {

    /**
     * 防止重复提交锁的过期时间
     *
     * @return 重复提交锁的过期时间
     */
    long duration() default 500;

    /**
     * 时长单位
     */
    TimeUnit timeUnit() default TimeUnit.MILLISECONDS;

    /**
     * 重试次数,0 代表不重试,默认不重试
     *
     * @return 重试次数
     */
    int retryTimes() default 0;

    /**
     * 锁定类型 0 处理完成后解锁, 1 处理完成不解锁,等待自动过期
     * @return 锁定类型
     */
    int lockType() default 0;

}

AOP切面定义

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 防止重复提交切面
 * @author 
 */
@Log4j2
@Component
@Aspect
public class RejectRepeatedSubmitAspect {

    @Autowired
    private SimpleRedisLockUtil simpleRedisLockUtil;

    @Pointcut("@annotation(com.yunti.wanmo.annotation.RejectRepeatedSubmit)")
    public void pointCut() {
    }


    @Around("pointCut()")
    public Object invoke(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        String simpleName = signature.getDeclaringType().getName().replace(".", ":");
        String methodName = simpleName + ":" + method.getName();
        RejectRepeatedSubmit annotation = method.getAnnotation(RejectRepeatedSubmit.class);
        String keyPrefix = RedisConstant.genKey(RedisConstant.NAMESPACE_WANMO, RedisConstant.TYPE_STRING, RedisBizConstant.REJECT_REPEATED_SUBMIT_PREFIX);
        String uniqueKey = keyPrefix + getUniqueKey(point.getArgs(), annotation, methodName);
        boolean lock = false;
        try {
            for (int retry = annotation.retryTimes() + 1; retry > 0; --retry) {
                lock = this.simpleRedisLockUtil.lock(uniqueKey, annotation.timeUnit().toMillis(annotation.duration()));
                if (lock) {
                    break;
                }
            }
            if (lock) {
                return point.proceed();
            }
            throw new BizException("操作频繁,请稍后再试...");
        } finally {
            if (lock && Objects.equals(0, annotation.lockType())) {
                simpleRedisLockUtil.unLock(uniqueKey);
            }
        }
    }

    /**
     * 注解与参数配合获取唯一标识
     *
     * @param args       The method params
     * @param annotation {@link RejectRepeatedSubmit}
     * @param methodName
     * @return
     */
    private String getUniqueKey(Object[] args, RejectRepeatedSubmit annotation, String methodName) {
        StringBuilder uniqueKey = new StringBuilder(methodName);
        if (args.length > 0) {
            for (Object arg : args) {
                Class<?> paramClass = null;
                if (Objects.nonNull(arg)) {
                    paramClass = arg.getClass();
                }
                String processedParam = null;
                try {
                    processedParam = processParams(arg, paramClass, true);
                } catch (Exception e) {
                    log.error("参数处理异常", e);
                }
                if (Objects.nonNull(processedParam)) {
                    uniqueKey.append("@");
                    uniqueKey.append(processedParam);
                }
            }
        }
        return uniqueKey.toString();
    }


    /**
     * 处理参数
     *
     * @param param      The method param
     * @param paramClass param class type
     * @return 带全类名+方法名+唯一标识的键
     */
    private <P> String processParams(Object param, Class<P> paramClass, boolean isContinue) {
        String uniqueKey;
        // 处理参数 基本类型
        if (Objects.isNull(param)) {
            uniqueKey = "null";
        } else if (param instanceof LoginDTO) {
            uniqueKey = ((LoginDTO) param).getUserId().toString();
        } else if (paramClass.isPrimitive()) {
            uniqueKey = String.valueOf(param);
        } else if (param instanceof String) {
            // String 类型
            uniqueKey = (String) param;
        } else if (param instanceof Number) {
            // 数值类型
            uniqueKey = String.valueOf(param);
        } else {
            if (!paramClass.getName().contains("com.yunti")) {
                return null;
            }
            if (isContinue) {
                StringBuilder obj = new StringBuilder();
                Field[] fields = param.getClass().getDeclaredFields();
                for (int i = 0, size = fields.length; i < size; i++) {
                    if (i > 0) {
                        obj.append("@");
                    }
                    try {
                        fields[i].setAccessible(true);
                        Object value = fields[i].get(param);
                        String objParam = processParams(value, value.getClass(), false);
                        if (Objects.nonNull(objParam)) {
                            obj.append(objParam);
                        }
                    } catch (IllegalAccessException e) {
                        log.error("参数处理异常", e);
                    }
                }
                return obj.toString();
            } else {
                // 对象,暂时md5值判断
                String jsonString = JSON.toJSONString(param);
                uniqueKey = DigestUtils.md5DigestAsHex(jsonString.getBytes());
            }
        }
        return uniqueKey;
    }


}
posted @   Cv工程师120621号  阅读(280)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· Vue3状态管理终极指南:Pinia保姆级教程
点击右上角即可分享
微信分享提示

目录导航