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

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

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

这篇文章我们介绍其中一种方案--借助Redis语法实现简单锁,最终实现防重提交。

背景:

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

后端代码可以在2个位置做控制:
一是放在gateway网关做

好处是只在一个地方加上控制代码,就可以控制所有接口的重复提交问题。坏处是控制的范围太广(比如查询接口无需控制,控制了反而多余)、定义重复提交的时间段不能灵活调整。
二是放在AOP切面做

好处是只有需要的地方才会被控制(哪里需要引用一下自定义注解即可),另外也能灵活调整定义重复提交的时间段(自定义注解里定义时间字段开放给使用者填写)。坏处是每个需要控制的地方都要加注解,会有侵入性和一定的工作量。

实现代码

1、添加自定义注解

java 复制代码
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切面

java 复制代码
package com.xxx.aop;

import com.crdigital.intelligent.user.common.core.annotations.RepeatSubmit;
import com.crdigital.intelligent.user.common.core.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.crdigital.intelligent.user.common.core.annotations.RepeatSubmit)")
    public void repeatSubmit() {}

    @Around("repeatSubmit()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        String uri = request.getRequestURI();
        String cacheKey = KEY_PREFIX.concat(uri);
        Boolean flag = null;
        try {
            flag = redisTemplate.opsForValue().setIfAbsent(cacheKey, "", annotation.expireTime(), TimeUnit.MILLISECONDS);
        } catch (Exception e) {
            log.error("", e);
            return joinPoint.proceed();
        }
        if (flag) {
            return joinPoint.proceed();
        } else {
            throw new ServiceException("系统繁忙,请稍后重试");
        }
    }
}

这里利用redisTemplate的setIfAbsent()实现的,如果存在就不能set成功,set的同时设置过期时间,可以是用使用默认,也可以自己根据业务调整。

另外,cacheKey的定义,也可以根据自己的需要去调整,比如根据当前登录用户的userId、当前登录的token等。

3、使用

java 复制代码
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {

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

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

背景:

明天补充哈, 今天有事中断一下

实现代码

明天补充哈, 今天有事中断一下

相关推荐
胖咕噜的稞达鸭1 小时前
算法入门:滑动窗口--->找到字符串中所有的字母异位词,串联所有的子串,最小覆盖子串
数据库·redis·算法
小坏讲微服务2 小时前
SpringCloud整合Scala实现MybatisPlus实现业务增删改查
java·spring·spring cloud·scala·mybatis plus
m***92382 小时前
docker中配置redis
redis·docker·容器
whltaoin3 小时前
【 手撕Java源码专栏 】Spirng篇之手撕SpringBean:(包含Bean扫描、注册、实例化、获取)
java·后端·spring·bean生命周期·手撕源码
秋邱3 小时前
价值升维!公益赋能 + 绿色技术 + 终身学习,构建可持续教育 AI 生态
网络·数据库·人工智能·redis·python·学习·docker
带刺的坐椅4 小时前
Solon 不依赖 Java EE 是其最有价值的设计!
java·spring·web·solon·javaee
Qiuner5 小时前
Spring Boot 机制二:配置属性绑定 Binder 源码解析(ConfigurationProperties 全链路)
java·spring boot·后端·spring·binder
Y***h18713 小时前
第二章 Spring中的Bean
java·后端·spring
8***293113 小时前
解决 Tomcat 跨域问题 - Tomcat 配置静态文件和 Java Web 服务(Spring MVC Springboot)同时允许跨域
java·前端·spring
多多*14 小时前
Java复习 操作系统原理 计算机网络相关 2025年11月23日
java·开发语言·网络·算法·spring·microsoft·maven