基于Redis分布式锁的防重复提交组件

防重复提交注解:Resubmit

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 重复提交标注注解
 *
 * @author Neo
 * @since 2021/12/13 10:05
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Resubmit {

    /**
     * 锁定时间(单位:秒,默认:10s)
     */
    int seconds() default 10;
}

防重复提交生成 token 工具类:ResubmitUtils

import cn.hutool.crypto.digest.MD5;
import com.google.common.collect.Lists;
import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.io.InputStreamSource;

import java.io.File;
import java.util.List;
import java.util.Objects;

/**
 * 重复提交工具类
 *
 * @author Neo
 * @since 2021/12/13 10:01
 */
public class ResubmitUtils {


    private ResubmitUtils() {
    }

    /**
     * 重复提交标记
     */
    public static final String RESUBMIT_KEY = "resubmit:";

    /**
     * 默认锁定时间:10s
     */
    public static final int DEFAULT_LOCK_SECOND = 10;

    private static final ResubmitExclusionStrategy EXCLUSION_STRATEGY;
    private static final Gson RESUBMIT_GSON;

    static {
        EXCLUSION_STRATEGY = new ResubmitExclusionStrategy();
        RESUBMIT_GSON = new GsonBuilder()
                .serializeNulls()
                .addSerializationExclusionStrategy(EXCLUSION_STRATEGY)
                .create();
    }

    /**
     * 生成重复提交 Token
     * 将参数序列化后进行 MD5 加密
     *
     * @author Neo
     * @since 2021/12/14 14:05
     */
    public static String token(Object o) {
        if (Objects.isNull(o)) {
            return StringUtils.EMPTY;
        }

        String json = RESUBMIT_GSON.toJson(o);

        return MD5.create().digestHex(json);
    }


    /**
     * 重复提交序列化策略
     *
     * @author Neo
     * @since 2021/12/14 14:03
     */
    private static class ResubmitExclusionStrategy implements ExclusionStrategy {
        public static final List<Class<?>> EXCLUSION_CLASS = Lists.newArrayList(
                InputStreamSource.class,
                File.class
        );

        @Override
        public boolean shouldSkipField(FieldAttributes f) {
            return false;
        }

        @Override
        public boolean shouldSkipClass(Class<?> clazz) {
            for (Class<?> exclusionClass : EXCLUSION_CLASS) {
                if (exclusionClass.isAssignableFrom(clazz)) {
                    return true;
                }
            }
            return false;
        }
    }
}

防重复提交拦截器:ResubmitInterceptor

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.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * 重复提交拦截器
 *
 * @author Neo
 * @since 2021/12/13 10:03
 */
@Aspect
@Component
public class ResubmitInterceptor {

    @Resource(name = "distributedLock")
    private ILock distributedLock;


    @Pointcut("@annotation(com.ceair.shoppingservice.common.util.resubmit.Resubmit)")
    public void pointcutOfAnnotation() {
    }


    @Around("pointcutOfAnnotation()")
    public Object around(ProceedingJoinPoint joinPoint) {
        String key = lockKey(joinPoint);
        int lockSecond = lockSecond(joinPoint);
        boolean locked = distributedLock.tryLock(key, lockSecond);
        BaseBusinessException.ifThrow(!locked, ResultCode.RESUBMIT_EXCEPTION);

        try {
            return joinPoint.proceed();
        } catch (BaseBusinessException e) {
            distributedLock.unlock(key);
            throw e;
        } catch (Throwable throwable) {
            distributedLock.unlock(key);
            SysLogHelper.error("重复提交捕获业务处理异常", throwable);
            throw new BaseBusinessException(ResultCode.SYSTEM_EXCEPTION);
        }
    }


    /**
     * 方法名
     *
     * @author Neo
     * @since 2021/12/14 14:26
     */
    public static String methodName(JoinPoint joinPoint) {
        return joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
    }

    /**
     * 锁定 Key
     *
     * @author Neo
     * @since 2021/12/14 15:11
     */
    public static String lockKey(JoinPoint joinPoint) {
        String methodName = methodName(joinPoint);
        List<Object> params = new ArrayList<>();
        for (Object arg : joinPoint.getArgs()) {
            if (arg instanceof HttpServletRequest || arg instanceof HttpServletResponse) {
                continue;
            }
            params.add(arg);
        }
        String token = ResubmitUtils.token(params);
        return ResubmitUtils.RESUBMIT_KEY + methodName + ":" + token;
    }

    /**
     * 获取锁定时间
     *
     * @author Neo
     * @since 2021/12/14 15:03
     */
    public static int lockSecond(JoinPoint joinPoint) {
        try {
            Class<?> targetClass = joinPoint.getTarget().getClass();
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();

            Method targetMethod = targetClass.getDeclaredMethod(signature.getName(), signature.getParameterTypes());
            Resubmit annotation = targetMethod.getAnnotation(Resubmit.class);

            return Objects.isNull(annotation) ? ResubmitUtils.DEFAULT_LOCK_SECOND : annotation.seconds();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return ResubmitUtils.DEFAULT_LOCK_SECOND;
    }
}

需要保证 ResubmitInterceptor 被 spring 容器加载到

你可能感兴趣的:(常用工具类,笔记,开发日常,redis,分布式,java,重复提交)