AOP实现防止前端请求重复提交

AOP实现防止前端请求重复提交

lixiangrong
2024-01-03 / 0 评论 / 15 阅读 / 正在检测是否收录...

1.防止重复提交注解

package com.risesun.common.core.annotation;

import java.lang.annotation.*;

/**
 * @Author LiXiangrong
 * @Description 防止重复提交注解
 * @Date 2023/04/03 9:04:15
 **/
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatSubmit
{

    /**
     * 防重复操作限时标记数值(存储redis限时标记数值)
     */
    String value() default "value" ;

    /**
     * 防重复操作过期时间(借助redis实现限时控制)
     */
    long expireSeconds() default 10;
}

2.AOP实现防止请求重复提交

package com.risesun.business.aspect;

import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.json.JSONUtil;
import com.risesun.common.core.annotation.RepeatSubmit;
import com.risesun.common.core.constant.TokenConstants;
import lombok.extern.slf4j.Slf4j;
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.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @Author LiXiangrong
 * @Description AOP实现防止请求重复提交
 * @Date 2023/04/03 8:41:04
 **/
@Slf4j
@Component
@Aspect
public class NoRepeatSubmitAspect
{
    @Autowired
    private RedisTemplate redisTemplate;

    private final String SIGN = "submit duplication";
    private final String PREFIX = "PREVENT_DUPLICATION_PREFIX:";

    /**
     * @Author LiXiangrong
     * @Description 定义切点
     * @Date 2023/04/03 8:53:11
     * @Return void
     **/
    @Pointcut("@annotation(com.risesun.common.core.annotation.RepeatSubmit)")
    public void preventDuplication() {}

    /**
     * @Author LiXiangrong
     * @Description 环绕方法
     * @Date 2023/04/03 8:52:50
     * @Param pjp
     * @Return java.lang.Object
     **/
    @Around("NoRepeatSubmitAspect.preventDuplication()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable
    {
        // 获取请求信息
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        // 获取执行方法
        Method method = ((MethodSignature) pjp.getSignature()).getMethod();
        //获取防重复提交注解
        RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);
        // 获取authorization以及方法标记,生成redisKey和redisValue
        String authorization = request.getHeader(TokenConstants.AUTHENTICATION);
        String url = request.getRequestURI();
        // 通过前缀 + url + token + 函数参数签名 来生成redis上的 key
        String redisKey = PREFIX.concat(url).concat(authorization).concat(getMethodSign(method, pjp.getArgs()));
        // 判断redisTemplate是否存在标记,如果没有,则是首次提交该请求,需要把设置标记并且正常返回
        if (!redisTemplate.hasKey(redisKey) && Objects.nonNull(annotation))
        {
            // 这个值只是为了标记,不重要
            String redisValue = redisKey.concat(annotation.value()).concat(SIGN);
            // 设置防重复操作的限时标记(前置通知)
            redisTemplate.opsForValue().set(redisKey, redisValue, annotation.expireSeconds(), TimeUnit.SECONDS);
            try
            {
                // 正常执行方法并返回 ProceedingJoinPoint类型参数可以决定是否执行目标方法,
                // 环绕通知必须要有返回值,返回值即为目标方法的返回值
                return pjp.proceed();
            } catch (Throwable throwable)
            {
                //确保方法执行异常实时释放限时标记(异常后置通知)
                redisTemplate.delete(redisKey);
                throw throwable;
            }
        } else
        {
            throw new RuntimeException("数据正在处理,请勿重复提交!");
        }
    }

    /**
     * @Author LiXiangrong
     * @Description 获取方法签名生成方法标记,采用数字签名算法SHA1对方法签名字符串加签
     * @Date 2023/04/03 8:49:45
     * @Param method
     * @Param args
     * @Return java.lang.String
     **/
    private String getMethodSign(Method method, Object... args)
    {
        StringBuilder sb = new StringBuilder(method.toString());
        for (Object arg : args)
        {
            sb.append(toString(arg));
        }
        return DigestUtil.sha1Hex(sb.toString());
    }

    /**
     * @Author LiXiangrong
     * @Description 对参数重写toString方法,避免空参造成的影响
     * @Date 2023/04/03 8:50:58
     * @Param arg
     * @Return java.lang.String
     **/
    private String toString(Object arg)
    {
        if (Objects.isNull(arg))
        {
            return "null";
        }
        if (arg instanceof Number)
        {
            return arg.toString();
        }
        return JSONUtil.toJsonStr(arg);
    }

}
0

评论 (0)

取消