使用Redis控制表单重复提交控制接口访问频率

这篇具有很好参考价值的文章主要介绍了使用Redis控制表单重复提交控制接口访问频率。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

场景一:控制表单重复提交

防重提交有很多方案,从前端的按钮置灰,到后端synchronize锁、Lock锁、借助Redis语法实现简单锁、Redis+Lua分布式锁、Redisson分布式锁,再到DB的悲观锁、乐观锁、借助表唯一索引等等都可以实现防重提交,以保证数据的安全性。
这篇文章我们介绍其中一种方案–借助Redis语法实现简单锁,最终实现防重提交。

背景:

我们项目中,为了控制表单重复提交问题,会在点击页面按钮(向后端发起业务请求)后就会置灰按钮,直到后端响应后解除按钮置灰。通过按钮置灰来防止重启提交问题。但Postman、Jmeter和其他服务调用(绕过前端页面)呢?所以后端接口也要根据控制表单重复提交的问题。

后端代码可以在2个位置做控制:
一是放在gateway网关做
好处是只在一个地方加上控制代码,就可以控制所有接口的重复提交问题。坏处是控制的范围太广(比如查询接口无需控制,控制了反而多余)、定义重复提交的时间段不能灵活调整。
二是放在AOP切面做
好处是只有需要的地方才会被控制(哪里需要引用一下自定义注解即可),另外也能灵活调整定义重复提交的时间段(自定义注解里定义时间字段开放给使用者填写)。坏处是每个需要控制的地方都要加注解,会有侵入性和一定的工作量。

实现代码

1、添加自定义注解

package com.xxx.annotations;

import java.lang.annotation.*;

/**
 * 自定义注解防止表单重复提交
 *
 * @Author WANGLINGQIANG
 * @Date 2023/9/6 10:11
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatSubmit {

    /**
     * 过期时间,单位毫秒
     */
    long expireTime() default 500L;

}

2、添加AOP切面

package com.xxx.aop;

import com.xxx.annotations.RepeatSubmit;
import com.xxx.exception.ServiceException;
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.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.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * 防止表单重复提交切面
 *
 * @Author WANGLINGQIANG
 * @Date 2023/9/6 10:13
 */
@Slf4j
@Aspect
@Component
public class RepeatSubmitAspect {
    private static final String KEY_PREFIX = "repeat_submit:";
    @Resource
    private RedisTemplate redisTemplate;

    @Pointcut("@annotation(com.xxx.annotations.RepeatSubmit)")
    public void repeatSubmit() {}

    @Around("repeatSubmit()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
    	//joinPoint获取方法对象
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        //获取方法上的@RepeatSubmit注解
        RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);
        //获取HttpServletRequest对象,以获取请求uri
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        String uri = request.getRequestURI();
        //拼接Redis的key,这里只是简单根据uri来判断是否重复提交。可以根据自己业务调整,比如根据用户id或者请求token等
        String cacheKey = KEY_PREFIX.concat(uri);
        Boolean flag = null;
        try {
            //借助setIfAbsent(),key不存在才能设值成功
            flag = redisTemplate.opsForValue().setIfAbsent(cacheKey, "", annotation.expireTime(), TimeUnit.MILLISECONDS);
        } catch (Exception e) {
            //如果Redis不可用,则打印日志记录,但依然对请求放行
            log.error("", e);
            return joinPoint.proceed();
        }
        //Redis可用的情况,如果flag=true说明单位时间内这是第一次请求,放行
        if (flag) {
            return joinPoint.proceed();
        } else {
            //进入else说明单位时间内进行了多次请求,则拦截请求并提示稍后重试
            throw new ServiceException("系统繁忙,请稍后重试");
        }
    }
}

这里利用redisTemplate的setIfAbsent()实现的,如果存在就不能set成功,set的同时设置过期时间,可以是用使用默认,也可以自己根据业务调整。
另外,cacheKey的定义,也可以根据自己的需要去调整,比如根据当前登录用户的userId、当前登录的token等。

3、使用

@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {

	@RepeatSubmit
    @PostMapping
    public AjaxResult add(@Validated @RequestBody SysUser user) {
    	//....
    }


场景二:控制接口调用频率

背景:

忘记密码后通过发送手机验证码找回密码的场景。因为每发一条短信都需要收费,所以要控制发短信的频率。比如,同一个手机号在3分钟内只能发送3次短信,超过3次后则提示用户“短信发送过于频繁,请10分钟后再试”。


实现代码

@Slf4j
@RestController
@RequestMapping("/sms")
public class SmsController {
    @Resource
    private ISmsService smsService;
    @Resource
    public RedisTemplate redisTemplate;

    @PostMapping("/sendValidCode")
    public Result sendValidCode(@RequestBody @Valid SmsDTO smsDTO) {
        //验证手机号格式
        checkPhoneNumber(smsDTO.getPhoneNumber());
        
        //...其他验证
        
		//拼接Redis的key(key为手机号,以控制一个手机号有限时间内容发送的次数)
        String cacheKey = "sms:code:resetPwd:"+smsDTO.getPhoneNumber();
        //验证发送短信次数,超过则拦截(阈值是3次,超时时间是3分钟,重试时间是10分钟)
        checkSendCount(cacheKey, THRESHOLD, TIMEOUT, RETRY_TIME);
        return smsService.sendMsg(smsDTO);
    }
    
    /**
     * 验证发送短信次数,超过则拦截
     * 该方法用lua脚本替换实现更好
     */
    private void checkSendCount(String cacheKey, Long threshold, Long timeout, String retryTime) {
   		//首先进方法就先+1
        Long count = redisTemplate.opsForValue().increment(cacheKey);
        //然后比较次数,是否超过阈值
        if (count > threshold) {
            //超过则设置过期时间为10分钟,并提示10分钟后重试
            redisTemplate.expire(cacheKey, 10L, TimeUnit.MINUTES);
            throw new ServiceException("短信发送过于频繁,请" + retryTime + "分钟后再试");
        } else {
            //没超过3次,则累加上这一次
            redisTemplate.expire(cacheKey, timeout, TimeUnit.MINUTES);
        }
    }

}

本章完结。

文章来源地址https://www.toymoban.com/news/detail-697475.html

到了这里,关于使用Redis控制表单重复提交控制接口访问频率的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • springboot3使用自定义注解+AOP+redis优雅实现防重复提交

      ⛰️个人主页:     蒾酒 🔥 系列专栏 :《spring boot实战》 🌊 山高路远,行路漫漫,终有归途 目录 写在前面 实现思路 实现步骤 1.定义防重复提交注解 2.编写一个切面去发现该注解然后执行防重复提交逻辑 3.测试 依赖条件 1.接口上标记防重复提交注解 2.接口测试 写在最

    2024年04月11日
    浏览(38)
  • HTML5中form表单防止重复提交的两种方法

    form表单重复提交是在多用户Web应用中最常见、带来很多麻烦的一个问题。有很多的应用场景都会遇到重复提交问题 (1)点击提交按钮两次。 (2)点击刷新按钮。 (3)使用浏览器后退按钮重复之前的操作 导致重复提交表单。 (4)浏览器重复的HTTP请求。 (5)用户提交表单时可能因为网

    2024年01月22日
    浏览(43)
  • 前端如何防止接口重复提交

    接口重复提交指的是在网络通信中,同一个请求被客户端多次发送到服务器端的情况。这种情况可能由于多种原因导致,例如用户在等待期间多次点击提交按钮、网络超时后客户端重新发送请求、客户端发送的请求在网络传输过程中出现重复等。 接口重复提交可能会导致多种

    2024年04月22日
    浏览(34)
  • SpringBoot 整合redis + Aop防止重复提交 (简易)

    redis下载 解压 安装 看一下就会有  进入redis-6.0.8下的src目录 (  src 目录下有编译后的 redis 服务程序 redis-server,还有用于测试的客户端程序 redis-cli:) 然后启动 redis默认端口号 6379,建议更改。redis.conf是配置文件在  与src是同级目录。 要远程  #去掉保护模式,注释掉bi

    2024年02月12日
    浏览(40)
  • SpringBoot限制接口访问频率 - 这些错误千万不能犯

    最近在基于SpringBoot做一个面向普通用户的系统,为了保证系统的稳定性,防止被恶意攻击,我想控制用户访问每个接口的频率。为了实现这个功能,可以设计一个annotation,然后借助AOP在调用方法之前检查当前ip的访问频率,如果超过设定频率,直接返回错误信息。 在开始介

    2024年02月05日
    浏览(45)
  • springboot实现后端防重复提交(AOP+redis分布式锁)单机情况下

    为什么要实现这个功能呢,可能用户在提交一份数据后,可能因为网络的原因、处理数据的速度慢等原因导致页面没有及时将用户刚提交数据的后台处理结果展示给用户,这时用户可能会进行如下操作: 1秒内连续点击提交按钮,导致重复提交表单。 使用浏览器后退按钮重复之

    2024年02月08日
    浏览(43)
  • 06_HTML_表单提交的细节(submit提交按钮的使用细节)

    示例 运行效果 点击提交后,会跳转至form标签中action属性所对应的链接,并将表单中的内容发给服务器。 (这里使用http://localhost:8080 地址演示提高效率) 随意填写表单内的内容,然后点击\\\"点击提交\\\" 成功跳转,并接收数据 这里我们分析地址: 这里会发现:?后面都是元素标

    2024年02月01日
    浏览(45)
  • 使用UE4 HttpRequest提交多表单

    大部分HTTP库都是支持直接设置多表单字段的,但UE4的HttpRequest比较惨,只能用SetContent设置整个的TArrayuint8作为请求体,所以想要传多表单就要自己拼。 首先设置Header,Content-Type设置为多表单,并设置boundary: boundary想设什么都行,但要和后面用的统一。 然后拼请求体的数据字

    2024年02月07日
    浏览(38)
  • 使用postman提交post方式的表单请求

    这里请求路径是测试数据库的,本地调试的话要换成自己的host和ip

    2024年02月12日
    浏览(54)
  • Vue表单提交正则表达式验证使用案例

    一、验证表单用法 1、表单布局及变量定义 2、定义表单中用到的实例、变量、交互dto等 3、验证规则表达式 说明: 1、sendRules 是表单使用的验证规则对象 2、属性 xxxNo 是具体校验那个属性字段 3、属性 required 非空校验 4、属性 message 提示字样 5、属性 trigger 触发事件 6、

    2024年02月16日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包