Springboot realizes anti-repetitive submission and anti-repetitive click (source code attached)

background#

The same data has been clicked multiple times by the user, resulting in data redundancy. It is necessary to prevent repeated clicks under weak network and other environments

aims#

By adding annotations to the specified interface, it can prevent repeated clicks according to the specified interface parameters

Description#

The repeated click here refers to clicking the button multiple times within a specified time period

Technical solutions#

springboot + redis lock + annotation

Use feign client for request testing

Springboot realizes anti-repetitive submission and anti-repetitive click (source code attached)

 

Final use case#

1. Determine the uniqueness based on the PathVariable parameter received by the interface

Copy/**
     *  根据请求参数里的 PathVariable 里获取的变量进行接口级别防重复点击
     *
     * @param testId 测试id
     * @param requestVo 请求参数
     * @return
     * @author daleyzou
     */
    @PostMapping("/test/{testId}")
    @NoRepeatSubmit(location = "thisIsTestLocation", seconds = 6)
    public RsVo thisIsTestLocation(@PathVariable Integer testId, @RequestBody RequestVo requestVo) throws Throwable {
        // 睡眠 5 秒,模拟业务逻辑
        Thread.sleep(5);
        return RsVo.success("test is return success");
    }

2. The value of the specified variable name in the RequestBody received by the interface is judged to be one

Copy/**
     *  根据请求参数里的 RequestBody 里获取指定名称的变量param5的值进行接口级别防重复点击
     *
     * @param testId 测试id
     * @param requestVo 请求参数
     * @return
     * @author daleyzou
     */
    @PostMapping("/test/{testId}")
    @NoRepeatSubmit(location = "thisIsTestBody", seconds = 6, argIndex = 1, name = "param5")
    public RsVo thisIsTestBody(@PathVariable Integer testId, @RequestBody RequestVo requestVo) throws Throwable {
        // 睡眠 5 秒,模拟业务逻辑
        Thread.sleep(5);
        return RsVo.success("test is return success");
    }

ps: jedis 2.9 has various compatibility issues with springboot, but I can only reduce the version of springboot

operation result#

Copy收到响应:{"succeeded":true,"code":500,"msg":"操作过于频繁,请稍后重试","data":null}
收到响应:{"succeeded":true,"code":500,"msg":"操作过于频繁,请稍后重试","data":null}
收到响应:{"succeeded":true,"code":500,"msg":"操作过于频繁,请稍后重试","data":null}
收到响应:{"succeeded":true,"code":200,"msg":"success","data":"test is return success"}

Test case#

Copypackage com.dalelyzou.preventrepeatsubmit.controller;
import com.dalelyzou.preventrepeatsubmit.PreventrepeatsubmitApplicationTests;
import com.dalelyzou.preventrepeatsubmit.service.AsyncFeginService;
import com.dalelyzou.preventrepeatsubmit.vo.RequestVo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
 * TestControllerTest
 * @description 防重复点击测试类
 * @author daleyzou
 * @date 2020年09月28日 17:13
 * @version 1.3.1
 */
class TestControllerTest extends PreventrepeatsubmitApplicationTests {
    @Autowired
    AsyncFeginService asyncFeginService;    @Test
    public void thisIsTestLocation() throws IOException {
        RequestVo requestVo = new RequestVo();
        requestVo.setParam5("random");
        ExecutorService executorService = Executors.newFixedThreadPool(4);
        for (int i = 0; i <= 3; i++) {
            executorService.execute(() -> {                String kl = asyncFeginService.thisIsTestLocation(requestVo);                System.err.println("收到响应:" + kl);
            });        }        System.in.read();    }    @Test
    public void thisIsTestBody() throws IOException {
        RequestVo requestVo = new RequestVo();
        requestVo.setParam5("special");
        ExecutorService executorService = Executors.newFixedThreadPool(4);
        for (int i = 0; i <= 3; i++) {
            executorService.execute(() -> {                String kl = asyncFeginService.thisIsTestBody(requestVo);                System.err.println("收到响应:" + kl);
            });        }        System.in.read();    }}

Define an annotation#

Copypackage com.dalelyzou.preventrepeatsubmit.aspect;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * NoRepeatSubmit
 * @description 重复点击的切面
 * @author daleyzou
 * @date 2020年09月23日 14:35
 * @version 1.4.8
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NoRepeatSubmit {
    /**
     * 锁过期的时间
     * */
    int seconds() default 5;
    /**
     * 锁的位置
     * */
    String location() default "NoRepeatSubmit";
    /**
     * 要扫描的参数位置
     * */
    int argIndex() default 0;
    /**
     * 参数名称
     * */
    String name() default "";
}

Define an aspect according to the specified annotation, and judge whether the request is repeated according to the specified value in the parameter#

Copypackage com.dalelyzou.preventrepeatsubmit.aspect;
import com.dalelyzou.preventrepeatsubmit.constant.RedisKey;
import com.dalelyzou.preventrepeatsubmit.service.LockService;
import com.dalelyzou.preventrepeatsubmit.vo.RsVo;
import com.google.common.collect.Maps;
import com.google.gson.Gson;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.lang.reflect.Field;
import java.util.Map;
@Aspect
@Component
public class NoRepeatSubmitAspect {
    private static final Logger logger = LoggerFactory.getLogger(NoRepeatSubmitAspect.class);
    private static Gson gson = new Gson();
    private static final String SUFFIX = "SUFFIX";
    @Autowired
    LockService lockService;    /**
     * 横切点
     */
    @Pointcut("@annotation(noRepeatSubmit)")
    public void repeatPoint(NoRepeatSubmit noRepeatSubmit) {
    }    /**
     *  接收请求,并记录数据
     */
    @Around(value = "repeatPoint(noRepeatSubmit)")
    public Object doBefore(ProceedingJoinPoint joinPoint, NoRepeatSubmit noRepeatSubmit) {
        String key = RedisKey.NO_REPEAT_LOCK_PREFIX + noRepeatSubmit.location();
        Object[] args = joinPoint.getArgs();
        String name = noRepeatSubmit.name();
        int argIndex = noRepeatSubmit.argIndex();        String suffix;
        if (StringUtils.isEmpty(name)) {
            suffix = String.valueOf(args[argIndex]);
        } else {
            Map<String, Object> keyAndValue = getKeyAndValue(args[argIndex]);
            Object valueObj = keyAndValue.get(name);
            if (valueObj == null) {
                suffix = SUFFIX;            } else {
                suffix = String.valueOf(valueObj);
            }        }        key = key + ":" + suffix;
        logger.info("==================================================");
        for (Object arg : args) {
            logger.info(gson.toJson(arg));        }        logger.info("==================================================");
        int seconds = noRepeatSubmit.seconds();        logger.info("lock key : " + key);
        if (!lockService.isLock(key, seconds)) {
            return RsVo.fail("操作过于频繁,请稍后重试");
        }        try {
            Object proceed = joinPoint.proceed();
            return proceed;
        } catch (Throwable throwable) {
            logger.error("运行业务代码出错", throwable);
            throw new RuntimeException(throwable.getMessage());
        } finally {
            lockService.unLock(key);        }    }    public static Map<String, Object> getKeyAndValue(Object obj) {
        Map<String, Object> map = Maps.newHashMap();
        // 得到类对象
        Class userCla = (Class) obj.getClass();
        /* 得到类中的所有属性集合 */
        Field[] fs = userCla.getDeclaredFields();
        for (int i = 0; i < fs.length; i++) {
            Field f = fs[i];
            // 设置些属性是可以访问的
            f.setAccessible(true);
            Object val = new Object();
            try {
                val = f.get(obj);
                // 得到此属性的值
                // 设置键值
                map.put(f.getName(), val);
            } catch (IllegalArgumentException e) {
                logger.error("getKeyAndValue IllegalArgumentException", e);
            } catch (IllegalAccessException e) {
                logger.error("getKeyAndValue IllegalAccessException", e);
            }
        }
        logger.info("扫描结果:" + gson.toJson(map));
        return map;
    }
}

Full address: https://github.com/daleyzou/PreventRepeatSubmit

Original link: https://www.cnblogs.com/daleyzou/p/noSubmitRepeat.html

If you think this article is helpful to you, you can forward it and follow it to support it, or you can follow my public account, there are more technical dry goods articles and related information sharing, everyone can learn and progress together!

Guess you like

Origin blog.csdn.net/weixin_50205273/article/details/108871823