对于表单提交的防止重复提交

本文介绍了如何利用Java的AOP和Redis来防止未登录用户在提交表单时的恶意重复点击。通过自定义的`@NoRepeat`和`@Tag`注解,标记需要唯一性的表单字段,如手机号或身份证,并在AOP切面中检查Redis缓存,避免重复提交。当表单数据已存在Redis中时,直接返回提示信息,有效减轻服务器和数据库的压力。
摘要由CSDN通过智能技术生成

场景:对于一些未登录的用户向系统提交表单信息时,不能拿到token来唯一标识用户,而用户恶意重复点击的情况。

方案:在表单中用自定义@Tag注解标记需要一个唯一的表单数据(比如:手机号,身份证等),使用AOP在切面中获取标记的属性值,组装放入redis并设置过期用户重复提交该条记录就不进行数据库判断,直接从redis获取数据返会重复提交,以次减少服务器和数据库压力。

代码:

注解NoRepeat :

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface NoRepeat {
    // 默认30s
    long time() default 30L;
}

注解Tag :

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Tag {
}

切面:

@Aspect
@Slf4j
@Configuration
public class NoRepeatAspect {
    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    private static final String DUPLICATE_COMMIT = "DUPLICATE_COMMIT:";

    /**
     * 切点
     */
    @Pointcut("@annotation(cn.zk.common.annatation.NoRepeat)")
    public void pointcut() {
    }

    @Around("pointcut()&&@annotation(nrp)")
    public Object around(ProceedingJoinPoint joinPoint, NoRepeat nrp) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Object proceed = null;
        StringBuffer cachePrefix = null;
        try {
            // 获取需要缓存的key
            cachePrefix = getTagString(joinPoint);
            String result = redisTemplate.opsForValue().get(DUPLICATE_COMMIT+cachePrefix.toString());
            if (!StringUtils.isEmpty(result)) {
                log.info("用户重复点击!==>key:{}", cachePrefix.toString());
                proceed = Response.failed(BizError.SYS_TIP, "请勿重复点击!");
            } else {
                proceed = joinPoint.proceed();
            }
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            log.error("重复点击切面错误==>key:{},err:[}", cachePrefix.toString(), throwable.getMessage());
            proceed = Response.failed(BizError.SYS_TIP, "系统错误!");
        } finally {
            if (!Objects.isNull(cachePrefix)) {
                redisTemplate.opsForValue().set(DUPLICATE_COMMIT+cachePrefix.toString(),
                        sdf.format(new Date()), nrp.time(), TimeUnit.SECONDS);
            }
        }
        return proceed;
    }

    /**
     * 获取参数中有Tag注解字段 并组装成redis的key
     *
     * @param joinPoint
     * @return
     * @throws IllegalAccessException
     */
    private StringBuffer getTagString(ProceedingJoinPoint joinPoint) throws IllegalAccessException {
        // 获取参数
        Object[] params = joinPoint.getArgs();
        if (params.length == 0) {
            return null;
        }
        //获取方法,此处可将signature强转为MethodSignature
        // API:获取连接点处的签名(用于跟踪或记录应用程序以获取有关连接点的反射信息)
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        //参数注解,1维是参数,2维是注解
        StringBuffer cachePrefix = new StringBuffer();
        // API:返回一个 Annotations 数组,1维是参数,2维是注解
        Annotation[][] annotations = method.getParameterAnnotations();
        for (int i = 0; i < annotations.length; i++) {
            Object param = params[i];
            Class<?> clz = param.getClass();
            // 扫描参数中的所有字段
            // API:返回一个 Field 对象数组,反映由此 Class 对象表示的类或接口声明的所有字段。
            // 这包括公共、受保护、默认(包)访问和私有字段,但不包括继承的字段
            for (Field field : clz.getDeclaredFields()) {
                field.setAccessible(true);
                if (field.isAnnotationPresent(Tag.class)) {
                    Tag tag = field.getAnnotation(Tag.class);
                    // API:返回指定对象上此字段表示的字段的值。如果该值具有原始类型,则该值会自动包装在对象中。
                    Object value = field.get(param);
                    if(!Objects.isNull(value)) {
                        cachePrefix.append(String.valueOf(value) + "-");
                    }
                }
            }
        }
        return cachePrefix;
    }

}

使用方式:

表单对象

 接口:

验证:

 第一次提交:

 30秒内重复提交:

 redis:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值