栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

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

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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

场景:对于一些未登录的用户向系统提交表单信息时,不能拿到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 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;
    }

    
    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:

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/844643.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号