使用Redisson自定义注解实现接口幂等性

通过定义@Idempotent注解和拦截器IdempotentInterceptor,利用Redisson的锁机制,确保重复请求的接口操作不会产生副作用。在拦截器中,检查请求方法上的@Idempotent注解,获取token并用作Redisson锁的key,防止并发时重复执行同一操作。异常情况抛出提示,请求完成后释放锁。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

要使用@Idempotent注解,你需要先在你的项目中引入Redisson的依赖。然后,你需要定义一个拦截器,用于拦截需要进行幂等性检查的接口。在该拦截器中,你需要获取当前请求所对应的方法,并判断该方法是否被@Idempotent注解所标记。如果是,则需要从请求头中获取token,并将其作为key,获取一个RLock对象。如果该RLock对象已经被其他请求锁定,则当前请求会抛出异常;否则,当前请求会获得该RLock对象并执行后续操作。当请求处理完成后,我们需要释放该RLock对象。

1.定义@Idempotent注解:

在你的项目中引入Redisson的依赖。

定义一个注解@Idempotent,用于标记需要进行幂等性检查的接口。

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
    String value() default "";
}

2. 定义拦截器

定义一个拦截器IdempotentInterceptor,用于拦截需要进行幂等性检查的接口。

@Component
public class IdempotentInterceptor extends HandlerInterceptorAdapter {

    @Autowired
    private RedissonClient redissonClient;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        Method method = ((HandlerMethod) handler).getMethod();
        Idempotent idempotent = method.getAnnotation(Idempotent.class);
        if (idempotent == null) {
            return true;
        }
        String token = request.getHeader("token");
        String key = "idempotent:" + token;
        RLock lock = redissonClient.getLock(key);
        boolean res = false;
        try {
            res = lock.tryLock(0, 10, TimeUnit.SECONDS);
            if (res) {
                return true;
            } else {
                throw new RuntimeException("请勿重复提交");
            }
        } finally {
            if (res) {
                lock.unlock();
            }
        }
    }
}

3. 注入拦截器

这一步把你开发好的拦截器注入容器使其生效

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private IdempotentInterceptor idempotentInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(idempotentInterceptor).addPathPatterns("/**");
    }
}

4.使用自定义注解

在需要进行幂等性检查的接口上添加@Idempotent注解。

@RestController
public class TestController {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @PostMapping("/test")
    @Idempotent
    public String test(@RequestBody Map<String, Object> params) {
        // your code here
    }
}
<think>好的,用户想了解在Spring Boot应用中防止重复提交表单的方法。首先,我需要回忆一下常见的解决方案。可能有前端和后端两种方式。前端的话,比如提交后禁用按钮,防止用户多次点击。但前端不可靠,因为用户可以绕过,所以后端也需要处理。 后端方面,我记得有几种方法。一种是使用Token机制,比如同步器令牌模式。每次生成表单时,服务器生成一个唯一Token,存到Session或者缓存里,提交的时候验证这个Token是否存在,存在就处理并删除,防止重复使用。这种方法应该可行,但需要考虑分布式环境下如何存储Token,可能需要用Redis这样的分布式缓存。 另一种方法可能是基于请求的指纹,比如用户ID、时间戳、参数等生成一个唯一标识,在一段时间内不允许重复提交。或者用注解的方式,通过AOP拦截请求,检查是否重复。比如自定义一个@NoDuplicateSubmission注解,结合拦截器或者过滤器来实现。 还有可能利用数据库的唯一约束,比如在提交时插入一条记录,包含请求的唯一标识,如果重复提交就会触发唯一约束异常,从而阻止重复处理。不过这种方法可能对数据库压力较大,需要权衡。 另外,可能还可以使用Spring的@Cacheable或者自定义的锁机制,比如在方法上锁,保证同一用户同一操作只能执行一次。或者使用分布式锁,比如Redisson,来处理集群环境下的并发问题。 需要验证这些方法的可行性。比如Token机制,用户可能打开多个标签页,这时候每个表单需要不同的Token。另外,处理完请求后需要及时删除Token,避免并发问题。而AOP的方式可能需要考虑方法的粒度,是全局还是针对某些接口。 还要考虑用户体验,比如前端禁用按钮后,如果后端处理时间较长,是否需要重新启用按钮。或者提供友好的提示,告知用户请求正在处理中,请勿重复提交。 可能还需要参考一些现有的库或者Spring生态中的组件,比如Spring Session,或者使用Hibernate Validator的自定义注解。或者利用拦截器,在preHandle方法中检查请求是否重复。 另外,HTTP的幂等性设计可能也是一个点,比如POST请求不是幂等的,但可以通过服务端设计使其幂等,比如使用PUT方法,或者服务端记录处理状态。 现在需要整理这些方法,给出具体的实现步骤,比如使用Token机制的具体代码示例,或者AOP拦截器的配置。同时要注意引用相关的资料,比如Spring官方文档中的防重复提交建议,或者社区的最佳实践。 可能还需要提到使用Redis来存储Token或者请求标识,确保在分布式环境下的一致性。比如,在生成表单时,将Token存入Redis,过期时间设置短一些,提交时从Redis中检查是否存在并删除。 另外,防止重复提交的时间窗口也需要考虑,比如同一用户同一操作在5秒内只能提交一次,可以通过Redis的过期时间来控制。 最后,需要总结这些方法的优缺点,比如前端方法简单但不可靠,后端Token机制可靠但需要额外处理,分布式环境下需要中间件支持等。根据用户的具体场景选择合适的方案。</think>在Spring Boot应用中防止重复提交表单的常见解决方案可分为前端控制与后端验证两类。以下是具体实现方案: ### 一、前端控制方案 1. **按钮禁用** 提交后立即禁用按钮并显示载状态: ```javascript document.getElementById('submitBtn').addEventListener('click', function() { this.disabled = true; this.innerHTML = '提交中...'; }); ``` *缺点:用户可能通过刷新页面绕过* ### 二、后端验证方案 #### 1. 同步器令牌模式(Synchronizer Token Pattern) **实现步骤**: - **生成Token**:在表单渲染时生成唯一Token ```java @GetMapping("/form") public String showForm(Model model, HttpSession session) { String token = UUID.randomUUID().toString(); session.setAttribute("FORM_TOKEN", token); // 生产环境建议用Redis model.addAttribute("token", token); return "form-page"; } ``` - **验证Token**:提交时校验并移除Token ```java @PostMapping("/submit") public String handleSubmit(@RequestParam String token, HttpSession session) { String sessionToken = (String) session.getAttribute("FORM_TOKEN"); if (sessionToken == null || !sessionToken.equals(token)) { throw new DuplicateSubmissionException("重复提交拒绝"); } session.removeAttribute("FORM_TOKEN"); // 处理业务逻辑 return "success"; } ``` #### 2. 基于AOP的注解控制 **自定义注解**: ```java @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface PreventDuplicate { int timeout() default 5; // 单位:秒 } ``` **AOP切面实现**: ```java @Aspect @Component public class DuplicateSubmitAspect { @Autowired private RedisTemplate<String, String> redisTemplate; @Around("@annotation(preventDuplicate)") public Object checkDuplicate(ProceedingJoinPoint joinPoint, PreventDuplicate preventDuplicate) throws Throwable { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); String userId = getCurrentUserId(); // 获取用户标识 String uri = request.getRequestURI(); String key = "SUBMIT:" + userId + ":" + uri; if (redisTemplate.opsForValue().get(key) != null) { throw new DuplicateSubmissionException("操作过于频繁"); } redisTemplate.opsForValue().set(key, "LOCKED", preventDuplicate.timeout(), TimeUnit.SECONDS); try { return joinPoint.proceed(); } finally { // 可根据业务需求决定是否立即删除 } } } ``` #### 3. 幂等性设计 对关键业务接口设计幂等性: ```java @PostMapping("/payment") public ResponseEntity<?> createPayment(@RequestBody PaymentRequest request, @RequestHeader("X-Idempotency-Key") String idempotencyKey) { if (paymentService.existsByIdempotencyKey(idempotencyKey)) { return ResponseEntity.ok().body(Map.of("status", "already_processed")); } // 处理支付逻辑 return ResponseEntity.ok().build(); } ``` ### 三、混合方案建议 1. **前端+后端双重验证** 组合按钮禁用与令牌验证,提升用户体验与安全性 2. **分布式环境处理** 使用Redis存储Token/锁,确保集群环境一致性[^1] 3. **异常处理优化** 返回明确错误码(如HTTP 429 Too Many Requests)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值