注解+拦截器实现防止表单重复提交

360 阅读3分钟

为何要防止表单重复提交

防止表单重复提交是一项常见的网络开发任务,可以有效地防止用户在提交表单时多次发送相同的请求,从而避免数据重复插入或其他不必要的副作用。

注解+拦截器实现防止表单重复提交

步骤

通过注解和拦截器是一种常见的在后端实现防止表单重复提交的方法。下面是一种基本的实现方式:

  1. 创建自定义注解:首先,创建一个自定义注解,例如 @PreventDuplicateSubmit​​,用于标记需要进行重复提交检测的方法或控制器。
  2. 编写拦截器:创建一个拦截器,在拦截器中实现对带有 @PreventDuplicateSubmit​​ 注解的请求进行处理。拦截器可以在请求到达目标方法之前进行拦截,并进行重复提交的检测。
  3. 实现重复提交检测逻辑:在拦截器中,可以使用会话(Session)或缓存(Cache)来保存已经处理过的请求的标识符。当拦截到一个请求时,首先判断该请求的标识符是否已经存在于会话或缓存中,如果存在,则判定为重复提交,并拒绝处理该请求。
  4. 注册拦截器:在后端的配置文件中,将拦截器注册到适当的位置,使其能够拦截带有 @PreventDuplicateSubmit​​ 注解的请求。
  5. 在目标方法或控制器中使用注解:在需要进行防止重复提交的方法或控制器上添加 @PreventDuplicateSubmit​​ 注解,以告知拦截器对该方法进行拦截和处理。

通过使用注解和拦截器的方式,可以将重复提交检测的逻辑与具体的方法或控制器解耦,提高代码的可维护性和复用性。同时,拦截器可以在请求到达处理方法之前进行拦截,因此可以更早地检测到重复提交,避免不必要的业务处理。

需要注意的是,具体的注解和拦截器的实现方式可能因使用的后端框架而有所不同。以上是一种常见的基本实现方式,具体实现细节可能会根据使用的框架和技术栈而有所变化。

代码

首先,创建一个自定义注解 PreventDuplicateSubmit​​:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PreventDuplicateSubmit {
}

接下来,创建一个拦截器 DuplicateSubmitInterceptor​​:

import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class DuplicateSubmitInterceptor implements HandlerInterceptor {

    private Map<String, Boolean> requestMap = new ConcurrentHashMap<>();
    private static final long EXPIRATION_TIME = 5000; // 5秒钟
    private ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            PreventDuplicateSubmit annotation = handlerMethod.getMethod().getAnnotation(PreventDuplicateSubmit.class);
            if (annotation != null) {
                HttpSession session = request.getSession();
                String sessionId = session.getId();
                String requestKey = getSessionKey(sessionId, request.getRequestURI());
                if (requestMap.containsKey(requestKey)) {
                    // 重复提交
                    response.getWriter().write("Duplicate form submission detected.");
                    return false;
                }
                // 首次提交,将请求标识存入Map
                requestMap.put(requestKey, true);
                // 延迟一定时间后异步删除请求标识
                scheduleRequestCleanup(requestKey);
                return true;
            }
        }
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 不需要执行任何操作
    }

    private void scheduleRequestCleanup(String requestKey) {
        executorService.schedule(() -> requestMap.remove(requestKey), EXPIRATION_TIME, TimeUnit.MILLISECONDS);
    }

    private String getSessionKey(String sessionId, String requestURI) {
        return sessionId + "_" + requestURI;
    }
}

然后,在Spring Boot的配置类中注册拦截器:

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new DuplicateSubmitInterceptor());
    }
}

最后,在需要进行重复提交检测的方法上添加 @PreventDuplicateSubmit​​ 注解:

@RestController
public class FormController {

    @PreventDuplicateSubmit
    @PostMapping("/submitForm")
    public String submitForm(@RequestBody Form form) {
        // 处理表单提交逻辑
        return "success";
    }
}

上述代码是一个基于Spring Boot的示例,通过自定义注解和拦截器来实现防止表单重复提交的功能。


推荐:

  1. Java后端面试题