SpringBoot后端限制重复提交

RequestBodyAdviceAdapter类

RequestBodyAdviceAdapter 是 Spring MVC 框架提供的一个抽象类,它实现了 RequestBodyAdvice 接口,并为该接口的所有方法提供了默认的空实现。
它的主要作用是作为一个便捷的基类,让你能够拦截并自定义处理带有 @RequestBody 或 HttpEntity 参数的请求。通过继承它,你只需重写自己关心的方法,而无需实现接口中所有的方法。

核心代码

复制代码
/**
 * 重复提交拦截器 如果涉及前后端加解密的话  也可以通过继承RequestBodyAdvice来实现
 *
 * @author valarchie
 */
@ControllerAdvice(basePackages = "com.wudi")
@Slf4j
@RequiredArgsConstructor
public class UnrepeatableInterceptor extends RequestBodyAdviceAdapter {

    private final RedisUtil redisUtil;

// 判断是否需要拦截:只有方法上标了@Unrepeatable注解的才处理
    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType,
        Class<? extends HttpMessageConverter<?>> converterType) {
        return methodParameter.hasMethodAnnotation(Unrepeatable.class);
    }

    /**
     * @param body 仅获取有RequestBody注解的参数
     */
    @NotNull
    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
        Class<? extends HttpMessageConverter<?>> converterType) {
        // 仅获取有RequestBody注解的参数
       // 1️⃣ 把当前请求参数转成JSON字符串(相当于"拍照留证")
        String currentRequest = JSONUtil.toJsonStr(body);

        Unrepeatable resubmitAnno = parameter.getMethodAnnotation(Unrepeatable.class);
        if (resubmitAnno != null) {
 // 2️⃣ 获取注解配置,生成Redis键名
            String redisKey = resubmitAnno.checkType().generateResubmitRedisKey(parameter.getMethod());

            log.info("请求重复提交拦截,当前key:{}, 当前参数:{}", redisKey, currentRequest);
// 3️⃣ 从Redis取出上一次的请求记录
            String preRequest = redisUtil.getCacheObject(redisKey);
            if (preRequest != null) {
                boolean isSameRequest = Objects.equals(currentRequest, preRequest);
 // 4️⃣ 对比两次请求是否完全一样
                if (isSameRequest) {
 // 重复了!拒绝处理
                    throw new ApiException(ErrorCode.Client.COMMON_REQUEST_RESUBMIT);
                }
            }
// 5️⃣ 不重复,把这次请求存到Redis,设置过期时间
            redisUtil.setCacheObject(redisKey, currentRequest, resubmitAnno.interval(), TimeUnit.SECONDS);
        }

        return body;
    }

}

Redis 中的set和setnx

Redis 的 SET

无论 key 是否存在,都会写入/覆盖

这里的逻辑是:先 GET 查一下有没有旧值 → 比较内容 → 再 SET 写入新值

这是一个 "读-比较-写" 三步操作,不是原子的

Redis的 SET if Not eXists

只有 key 不存在时才写入,已存在则写入失败

是原子操作,一步完成"检查+写入"

用setNull改造后的时序图

SetNx常见的坑以及注意的点

// 原子操作:尝试写入,成功说明是首次请求,失败说明key已存在

Boolean isFirst = redisUtil.setIfAbsent(redisKey, currentRequest, resubmitAnno.interval(), TimeUnit.SECONDS);

if (Boolean.FALSE.equals(isFirst)) {

// key已存在,再比较内容确认是否真的重复

String preRequest = redisUtil.getCacheObject(redisKey);

if (Objects.equals(currentRequest, preRequest)) {

throw new ApiException(ErrorCode.Client.COMMON_REQUEST_RESUBMIT);

}

// 内容不同则更新为最新请求

redisUtil.setCacheObject(redisKey, currentRequest, resubmitAnno.interval(), TimeUnit.SECONDS);

}

两者的使用场景

相关推荐
周杰伦fans21 分钟前
8GB显存下模型量化实战指南
人工智能·后端·c#
余槐i32 分钟前
数据没回滚也不报错:@Transactional 自调用失效的 3 种复现与修复
spring boot·mysql·多线程·spring 事务
HEJOO934 分钟前
指针的算术运算详解
java·数据结构·算法
sunshine22 girl35 分钟前
Java学习六 Java常见API-2--String-3-字符串的截取替换和其他方法
java·开发语言·学习
EatFan1 小时前
从“框架混战“到“运行时收敛“:2026 年 AI Agent 开发框架的三条路线之争
java·数据库·人工智能·多智能体·ai agent·mcp·agent 框架
小蒜学长1 小时前
基于SpringBoot的佳新超市管理系统设计与实现系统(代码+数据库+LW)
java·数据库·spring boot·后端·佳新超市管理系统
sunshine22 girl1 小时前
Java学习六 Java常见API-1-Radom随机数
java·开发语言·学习
IT_陈寒2 小时前
Vite静态资源导入这个坑我帮你们踩过了
前端·人工智能·后端
wangbing11252 小时前
使用jps jstack排查系统问题
java
于指尖飞舞2 小时前
java面试题总结
java·开发语言