java后端接口防止表单重复提交

导入依赖

<!-- aspectj -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.8.13</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.13</version>
        </dependency>

方案一:使用本地锁

1、定义一个防止提交的注解

import java.lang.annotation.*;

/**
 * 防止重复提交的注解:
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Resubmit {

    /**
     * 延时时间 在延时多久后可以再次提交,默认为20
     *
     * @return Time unit is one second
     */
    int delaySeconds() default 20;

}


2、新建一个“锁”,通过对参数进行加锁和放锁来防止重复提交;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;

import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 防止重复提交的锁
 */
@Slf4j
public final class ResubmitLock {

    /**
     * 新建一个用于存放key的容器,容量为20;
     */
    private static final ConcurrentHashMap<String, Object> LOCK_CACHEMAP = new ConcurrentHashMap<>(200);

    /**
     * 新建一个可定时线程池:
     *    - 核心线程数为5;
     *    - 设置任务数超过线程池容量以及任务队列的容量时的处理程序,这里是默默丢弃掉新来的任务,并抛出一个RejectedExecutionHandler拒绝处理异常
     */
    private static final ScheduledThreadPoolExecutor EXECUTOR = new ScheduledThreadPoolExecutor(5, new ThreadPoolExecutor.DiscardPolicy());


    private ResubmitLock() {
    }

    /**
     * 单例模式,保证同一时间内只生成一个锁的实例;
     *          ——这里采用了静态内部类的方式;
     * @return
     */
    private static class SingletonInstance {
        private static final ResubmitLock INSTANCE = new ResubmitLock();
    }

    public static ResubmitLock getInstance() {
        return SingletonInstance.INSTANCE;
    }

    // 对参数进行md5加密:
    public static String handleKey(String param) {
        return DigestUtils.md5Hex(param == null ? "" : param);
    }

    /**
     * 加锁:
     *          putIfAbsent 是原子操作,保证线程安全
     *          putIfAbsent在放入数据时,如果存在重复的key,那么putIfAbsent不会放入值,会返回存在的value,不进行替换
     * @param key   对应的key
     * @param value
     * @return
     */
    public boolean lock(final String key, Object value) {
        //如果之前不存在该key,才会将该key和value存储起来,并返回true,
        // 如果之前存在该key,这里会返回false;
        return Objects.isNull(LOCK_CACHEMAP.putIfAbsent(key, value));
    }

    /**
     * 延时释放锁, 用以控制指定时间内的重复提交
     *
     * @param lock         是否需要解锁
     * @param key          对应的key
     * @param delaySeconds 延时时间
     */
    public void unLock(final boolean lock, final String key, final int delaySeconds) {
        if (lock) {
            //EXECUTOR.schedule(),执行定时任务;
            EXECUTOR.schedule(() -> {
                LOCK_CACHEMAP.remove(key);
            }, delaySeconds, TimeUnit.SECONDS);
        }
    }
}

3、定义切面,完成环绕增强的逻辑,在里边进行加锁和放锁来实现防止重复提交:

import com.alibaba.fastjson.JSONObject;
import com.yscz.bs.bean.RespBean;
import lombok.extern.log4j.Log4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * 数据重复提交校验
 **/
@Log4j
@Aspect
@Component
public class ResubmitDataAspect {

    private final static String DATA = "data";
    //因为key容器是一个Map类型,所以PRESENT是作为一个僵尸value、用于存储key的;
    private final static Object PRESENT = new Object();

    /**
     * 处理重复提交的方法:
     * @param joinPoint 连接点对象
     *                  Proceedingjoinpoint 继承了 JoinPoint,是在JoinPoint的基础上暴露出 proceed 这个方法。
     *                  proceed很重要,这个是aop代理链执行的方法。
     *                  环绕通知=前置+目标方法执行+后置通知,proceed方法就是用于启动目标方法执行的;
     *                  暴露出proceed这个方法,就能支持 aop:around 这种切面,就能走代理链中的增强方法;
     *                  (而其他的几种切面只需要用到JoinPoint,这也是环绕通知和前置、后置通知方法的一个最大区别。这跟切面类型有关),
     *                  建议看一下 JdkDynamicAopProxy的invoke方法,了解一下代理链的执行原理。
     * @return
     * @throws Throwable
     */
    @Around("@annotation(Resubmit)") //环绕增强
    public Object handleResubmit(ProceedingJoinPoint joinPoint) throws Throwable {
        //获取此连接点对象上的加@Resubmit防止重复提交注解的方法:
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        //获取注解信息,比如延迟时间:
        Resubmit annotation = method.getAnnotation(Resubmit.class);
        int delaySeconds = annotation.delaySeconds();
        Object[] pointArgs = joinPoint.getArgs();
        String key = "";
        //获取用户传进来的(连接点对象的)第一个参数
        Object firstParam = pointArgs[0];
        if (firstParam instanceof RespBean) {
            //解析参数
            JSONObject requestDTO = JSONObject.parseObject(firstParam.toString());
            //获取到该参数的数据值:
            JSONObject data = JSONObject.parseObject(requestDTO.getString(DATA));
            if (data != null) {
                StringBuffer sb = new StringBuffer();
                data.forEach((k, v) -> {
                    sb.append(v);
                });
                //对该参数的数据值进行加密,使用了content_MD5的加密方式
                key = ResubmitLock.handleKey(sb.toString());
            }
        }
        //对该参数值执行加锁
        boolean isLock = false;
        try {
            //如果是第一次提交,那么key容器内还没有该key参数,则返回true,加锁成功;
            isLock = ResubmitLock.getInstance().lock(key, PRESENT);
            if (isLock) {
                //放行,进行AOP代理链中的下一个增强方法的调用
                return joinPoint.proceed();
            }
            //如果是第二次第三次等重复提交,此时key容器内已有该key参数,则返回false,加锁失败;
            else {
                //抛出重复提交异常
                return RespBean.error("重复提交。。");
            }
        } finally {
            //设置解锁key和解锁时间,到时间了自动从key容器内移除该key;
            ResubmitLock.getInstance().unLock(isLock, key, delaySeconds);
        }
    }

}

4、使用@Resubmit注解,防止重复提交:

@RestController
public class TestController {
 
      @PostMapping("/test")
      @Resubmit(delaySeconds = 10) //加了防止重复提交的注解
      public ResponBean saveOrder(Map mapper) {
            // TODO
            return new ResponBean();  //模拟返回结果
      }
}

你可能感兴趣的:(笔记,java,开发语言)