SpringBoot接口防抖(防重复提交)的一些实现方案

360影视 2025-01-22 08:50 2

摘要:// 生成Token并存储在Session中@GetMapping("/form")public String showForm(HttpSession session) { String token = UUID.randomUUID.toString; s

在Spring Boot应用中,接口防抖(也称为防重复提交)是一个常见需求,特别是在处理表单提交、支付请求等敏感操作时。以下是一些常见的实现方案:

// 生成Token并存储在Session中@GetMapping("/form")public String showForm(HttpSession session) { String token = UUID.randomUUID.toString; session.setAttribute("token", token); return "formPage"; // 返回表单页面,页面中包含隐藏的Token字段}// 提交表单时验证Token@PostMapping("/submit")public ResponseEntity submitForm(@requestParam("token") String token, Httpsession session) { String sessionToken = (String) session.getAttribute("token"); if (sessionToken == null || !sessionToken.equals(token)) { return ResponseEntity.badRequest.body("Invalid or used token"); } // 处理请求 session.removeAttribute("token"); // 标记为已使用 return ResponseEntity.ok("Form submitted successfully");}@Autowiredprivate RedisTemplate redisTemplate;// 生成唯一请求标识private String generateRequestId(HttpServletRequest request) { String userId = // 获取用户ID的方式 String uri = request.getRequestURI; String queryString = request.getQueryString; return userId + ":" + uri + (queryString != null ? ":" + queryString : "");}@PostMapping("/submit")public ResponseEntity submitForm(HttpServletRequest request) { String requestId = generateRequestId(request); String lockKey = "requestlock:" + requestId; Boolean isLocked = (Boolean) redisTemplate.opsForValue.get(lockKey); if (isLocked != null && isLocked) { return ResponseEntity.badRequest.body("Duplicate request detected"); } // 锁定请求,设置过期时间 redisTemplate.opsForValue.set(lockKey, "true", 30, TimeUnit.SECONDS); // 处理请求 // ... return ResponseEntity.ok("Form submitted successfully");}// 自定义注解@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface Debounce {}// AOP拦截器@Aspect@Componentpublic class DebounceAspect { @Autowired private RedisTemplate redisTemplate; @Around("@annotation(debounce)") public Object debounce(ProceedingJoinPoint joinPoint, Debounce debounce) throws Throwable { MethodSignature signature = (MethodSignature) joinPoint.getSignature; Method method = signature.getMethod; Object args = joinPoint.getArgs; HttpServletRequest request = (HttpServletRequest) args[0]; // 假设第一个参数是HttpServletRequest String userId = // 获取用户ID的方式 String requestId = userId + ":" + method.getDeclaringClass.getName + ":" + method.getName; for (Object arg : args) { if (arg instanceof HttpServletRequest || arg instanceof HttpServletResponse) continue; requestId += ":" + arg.toString; } String lockKey = "requestLock:" + requestId; Boolean isLocked = (Boolean) redisTemplate.opsForValue.get(lockKey); if (isLocked != null && isLocked) { throw new RuntimeException("Duplicate request detected"); } redisTemplate.opsForValue.set(lockKey, "true", 30, TimeUnit.SECONDS); return joinPoint.proceed; }}

以上方案各有优缺点,选择哪种方案取决于具体需求和系统架构。例如,基于Token的方案实现简单,但依赖于Session;基于Redis的方案更适合分布式系统,但需要额外的Redis组件;基于AOP的方案灵活且易于维护,但需要一定的Spring AOP知识。

来源:散文随风想

相关推荐