实现思路:首先定义一个拦截器,拦截所有请求,然后通过自定义注解在拦截器种判断该Controller的请求方法是否加上该注解,如果加上该注解,则执行拦截重复请求,如果第一次访问,则往redis新增一条记录,方法名和用户Token做为key,请求参数作为Value,如果再5秒Redis存在该请求则进行拦截并返回错误提示

Spring Boot项目需要引入的依赖

    <!-- spring boot整合redis -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <!-- 对象池,使用redis时必须引入 -->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
    </dependency>

   <!-- hutool工具类 -->
    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>5.8.0.M2</version>
    </dependency>

yml配置

spring:
  redis:
    #Redis 主机地址
    host: 106.75.148.160
    #Redis 主机端口
    port: 6389
    password: as@15017170054AS
    # 连接超时时间(记得添加单位,Duration)
    timeout: 10000ms
    # Redis默认情况下有16个分片,这里配置具体使用的分片
    # database: 0
    lettuce:
      pool:
        # 连接池最大连接数(使用负值表示没有限制) 默认 8
        max-active: 8
        # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
        max-wait: -1ms
        # 连接池中的最大空闲连接 默认 8
        max-idle: 8
        # 连接池中的最小空闲连接 默认 0
        min-idle: 0

如果你的Spring Boot项目已经整合好了Redis可以忽略上面两步

自定义去重提交注解

(1)我们首先定义一个注解,以后的controller中的接口如果需要防重复提交就加上这个注解:

注解中的value代表我们防止重复提交的默认时间:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Repeat {
    long value() default 10;
}
添加RedisUtil工具类

https://songzixian.com/javatuils/599.html

添加ResponseResult工具类

https://songzixian.com/javatuils/1084.html

CustomObjectMapper
/**
 * <p>
 * Description: TODO
 * </p>
 *
 * @author songzixian
 * @version v2.0.0
 * @create 2022-04-03 19:27
 * @see com.songzixian.mapper
 */
@Configuration
public class CustomObjectMapper extends ObjectMapper {

    public CustomObjectMapper() {
        super();
        //去掉默认的时间戳格式
        configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
        //设置为东八区
        setTimeZone(TimeZone.getTimeZone("GMT+8"));
        //设置日期转换yyyy-MM-dd HH:mm:ss
        setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        // 设置输入:禁止把POJO中值为null的字段映射到json字符串中
        configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false);
        // 空值不序列化
        setSerializationInclusion(JsonInclude.Include.NON_NULL);
        // 反序列化时,属性不存在的兼容处理
        getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        // 序列化枚举是以toString()来输出,默认false,即默认以name()来输出
        configure(SerializationFeature.WRITE_ENUMS_USING_TO_STRING, true);
    }
}

Constants

public class Constants {

    // 统一的token前缀
    public static final String TOKEN_PREFIX = "token:";
    // 权限请求头
    public static final String HEAD_AUTHORIZATION = "Authorization";
    // token的过期时间
    public static final Long TOKEN_TIME = 30*60L;
    // 角色前缀
    public static final String ROLE_PREFIX = "roles:";
    // 权限的前缀
    public static final String PERM_PREFIX = "perm:";
    // 防止重复提交的key前缀
    public static final String REPEAT_SUBMIT_KEY = "repeat:";
}
注册拦截器
/**
 * <p>
 * Description: TODO
 * </p>
 *
 * @author songzixian
 * @version v2.0.0
 * @create 2022-04-03 14:13
 * @see com.songzixian.config
 */
@Configuration
public class MyAppCofnig implements WebMvcConfigurer {

    private final RepeatSubmitInterceptor repeatSubmitInterceptor;

    public MyAppCofnig(RepeatSubmitInterceptor repeatSubmitInterceptor) {
        this.repeatSubmitInterceptor = repeatSubmitInterceptor;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注册自定义拦截器,添加拦截路径和排除拦截路径
        registry.addInterceptor(repeatSubmitInterceptor) // 添加拦截器
                .addPathPatterns("/**"); // 添加拦截拦截路径
        WebMvcConfigurer.super.addInterceptors(registry);
    }

}
RepeatSubmitInterceptor实现类
    import cn.hutool.json.JSONUtil;
    import com.songzixian.constants.Constants;
    import com.songzixian.interfaces.Repeat;
    import com.songzixian.mapper.CustomObjectMapper;
    import com.songzixian.util.RedisUtil;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.http.ResponseEntity;
    import org.springframework.stereotype.Component;
    import org.springframework.web.method.HandlerMethod;
    import org.springframework.web.servlet.HandlerInterceptor;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.lang.reflect.Method;
    import java.util.Map;
    
  
  /**
     * <p>
     * Description: TODO
     * </p>
     *
     * @author songzixian
     * @version v2.0.0
     * @create 2022-04-03 19:29
     * @see com.songzixian.service
     */
    @Component
    public class RepeatSubmitInterceptor implements HandlerInterceptor {
    
        @Autowired
        private CustomObjectMapper customObjectMapper;
    
        @Autowired
        private RedisUtil redisUtil;
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            if (handler instanceof HandlerMethod) {
                // 获取我们的controller方法
                HandlerMethod handlerMethod = (HandlerMethod) handler;
                Method method = handlerMethod.getMethod();
                // 判断是否有防止重复提交的注解
                Repeat annotation = method.getAnnotation(Repeat.class);
                if (annotation != null) {
                    if (this.isRepeatSubmit(request, annotation)) {
                    // 如果确定是重复提交,直接响应失败
                    // 自带格式
//                    ResponseEntity<String> body = ResponseEntity.status(500).body("您重复提交了当前的请求!");
//                    response.setStatus(500);
                    response.setContentType("application/json;charset=utf-8");
                    // 自定义格式
                    ResponseResult responseResult = new ResponseResult();
                    responseResult.setMessage("您重复提交了当前的请求");
                    responseResult.setCode(500);                    
                    response.getWriter().write(customObjectMapper.writeValueAsString(responseResult));
                    return false;
                    }
                }
                return true;
            } else {
                return true;
            }
        }
    
        public boolean isRepeatSubmit(HttpServletRequest request, Repeat annotation) throws IOException {
            // 获取参数列表,并序列化
            Map<String, String[]> parameterMap = request.getParameterMap();
    
            String nowParams = JSONUtil.toJsonStr(parameterMap);
    
            System.out.println("请求参数--->" + nowParams);
    
    
            // 请求地址(作为存放cache的key值)
            String url = request.getRequestURI();
    
            // 唯一值(没有消息头则使用请求地址)
            String token = request.getHeader(Constants.HEAD_AUTHORIZATION);
    
            // 获得body的数据
    
            // 唯一标识(指定key + url + 消息头)
            String cacheRepeatKey = Constants.REPEAT_SUBMIT_KEY + token + ":" + url;
            // 如果redis中没有存在这个key,说明这个请求是重复提交
            String preParams = redisUtil.getString(cacheRepeatKey);
    
            if (preParams != null && preParams.equals(nowParams)) {
                return true;
            }
            // 否则我就存入redis,注意设置过期时间
            redisUtil.setString(cacheRepeatKey, nowParams, annotation.value());
            return false;
        }
    }
Controller测试类

注解通常可以使用在add,update这一类接口上,delete和query是没有必要的,因为这两种接口本身就是幂等性的。
@Repeat(5) //5表示提交的时间/ms

/**
 * <p>
 * Description: 宋子宪博客
 * </p>
 *
 * @author songzixian
 * @version v2.0.0
 * @create 2022-04-03 19:18
 * @see com.songzixian.controller
 */
@Slf4j
@RestController
public class TestController {

    @Autowired
    private RedisUtil redisUtil;

    @Repeat(5) //加上@Repeat注解,表示该方法表单去重生效
    @GetMapping("test01")
    public String test01() {
        log.info("test01方法被调用类...");
        return "请求成功";
    }
}

当第一次请求可以正常访问 ps:利用token作为Redis的ket来区分不同用户的请求,所以请记得加上Token测试
http://localhost:8080/test01?openId=orysv50bn7TEujnRQ6Tc9F-wlXdMist
微信截图_20220402233740.png
请求成功后会完Redis插入一条记录
5秒内相同的方法和参数再次重复请求,会出现一下效果
微信截图2_20220404153349.png

Last modification:May 10, 2022
如果觉得这篇技术文章对你有用,请随意赞赏