前言:在日常开发中,客户可能会存在反复点击提交按钮导致表单的重复提交,这个问题也是非常需要重视的,在本篇博客中,采用的是session、自定义注解和拦截器的方式来防止重复表单的重复提交,提高整体代码的优雅和整洁度!
目录
一、核心实现思路
二、代码实现
1、编写自定义注解@RepeatSubmit
2、核心配置类
3、撰写拦截器核心代码
4、JSON工具类
5、编写请求层
6、运行结果
三、Gitee源码
一、核心实现思路
这边我用自己的理解方式进行的梳理,大概逻辑如下:
1、撰写一个自定义注解。
2、撰写自定义拦截器。
3、每次有请求之前都会进入此拦截器,判断当前的接口是否存在自定义的注解,不存在则直接放行请求。
4、比对当前传入的参数和之前存入session的参数以及时间差的别对是否成立,成立则为重复请求,拦截当前url请求,并返回错误信息。
二、代码实现
整体代码实现非常简洁以及通俗易懂,都是本人精心整理出来的。
1、编写自定义注解@RepeatSubmit
package com.example.repeat.annotation;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatSubmit
{
/**
* 间隔时间(ms),小于此时间视为重复提交
*/
public int interval() default 5000;
/**
* 提示消息
*/
public String message() default "不允许重复提交,请稍后再试";
}
2、核心配置类
package com.example.repeat.config;
import com.example.repeat.interceptor.RepeatSubmitInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
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 ResourcesConfig implements WebMvcConfigurer
{
@Autowired
private RepeatSubmitInterceptor repeatSubmitInterceptor;
/**
* 自定义拦截规则
*/
@Override
public void addInterceptors(InterceptorRegistry registry)
{
registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**");
}
}
3、撰写拦截器核心代码
package com.example.repeat.interceptor;
import com.example.repeat.annotation.RepeatSubmit;
import com.example.repeat.utils.JSON;
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 javax.servlet.http.HttpSession;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
@Component
public class RepeatSubmitInterceptor implements HandlerInterceptor {
public final String REPEAT_PARAMS = "repeatParams";
public final String REPEAT_TIME = "repeatTime";
public final String SESSION_REPEAT_KEY = "repeatData";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod)
{
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);
if (annotation != null)
{
if (this.isRepeatSubmit(request, annotation))
{
System.out.println(annotation.message());
return false;
}
}
return true;
}
else
{
return true;
}
}
public boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation) throws Exception
{
// 本次参数及系统时间
String nowParams = JSON.marshal(request.getParameterMap());
Map<String, Object> nowDataMap = new HashMap<String, Object>();
nowDataMap.put(REPEAT_PARAMS, nowParams);
nowDataMap.put(REPEAT_TIME, System.currentTimeMillis());
// 请求地址(作为存放session的key值)
String url = request.getRequestURI();
HttpSession session = request.getSession();
Object sessionObj = session.getAttribute(SESSION_REPEAT_KEY);
if (sessionObj != null)
{
Map<String, Object> sessionMap = (Map<String, Object>) sessionObj;
if (sessionMap.containsKey(url))
{
Map<String, Object> preDataMap = (Map<String, Object>) sessionMap.get(url);
if (compareParams(nowDataMap, preDataMap) && compareTime(nowDataMap, preDataMap, annotation.interval()))
{
return true;
}
}
}
Map<String, Object> sessionMap = new HashMap<String, Object>();
sessionMap.put(url, nowDataMap);
session.setAttribute(SESSION_REPEAT_KEY, sessionMap);
return false;
}
/**
* 判断参数是否相同
*/
private boolean compareParams(Map<String, Object> nowMap, Map<String, Object> preMap)
{
String nowParams = (String) nowMap.get(REPEAT_PARAMS);
String preParams = (String) preMap.get(REPEAT_PARAMS);
return nowParams.equals(preParams);
}
/**
* 判断两次间隔时间
*/
private boolean compareTime(Map<String, Object> nowMap, Map<String, Object> preMap, int interval)
{
long time1 = (Long) nowMap.get(REPEAT_TIME);
long time2 = (Long) preMap.get(REPEAT_TIME);
if ((time1 - time2) < interval)
{
return true;
}
return false;
}
}
4、JSON工具类
package com.example.repeat.utils;
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class JSON
{
public static final String DEFAULT_FAIL = "\"Parse failed\"";
private static final ObjectMapper objectMapper = new ObjectMapper();
private static final ObjectWriter objectWriter = objectMapper.writerWithDefaultPrettyPrinter();
public static void marshal(File file, Object value) throws Exception
{
try
{
objectWriter.writeValue(file, value);
}
catch (JsonGenerationException e)
{
throw new Exception(e);
}
catch (JsonMappingException e)
{
throw new Exception(e);
}
catch (IOException e)
{
throw new Exception(e);
}
}
public static void marshal(OutputStream os, Object value) throws Exception
{
try
{
objectWriter.writeValue(os, value);
}
catch (JsonGenerationException e)
{
throw new Exception(e);
}
catch (JsonMappingException e)
{
throw new Exception(e);
}
catch (IOException e)
{
throw new Exception(e);
}
}
public static String marshal(Object value) throws Exception
{
try
{
return objectWriter.writeValueAsString(value);
}
catch (JsonGenerationException e)
{
throw new Exception(e);
}
catch (JsonMappingException e)
{
throw new Exception(e);
}
catch (IOException e)
{
throw new Exception(e);
}
}
public static byte[] marshalBytes(Object value) throws Exception
{
try
{
return objectWriter.writeValueAsBytes(value);
}
catch (JsonGenerationException e)
{
throw new Exception(e);
}
catch (JsonMappingException e)
{
throw new Exception(e);
}
catch (IOException e)
{
throw new Exception(e);
}
}
public static <T> T unmarshal(File file, Class<T> valueType) throws Exception
{
try
{
return objectMapper.readValue(file, valueType);
}
catch (JsonParseException e)
{
throw new Exception(e);
}
catch (JsonMappingException e)
{
throw new Exception(e);
}
catch (IOException e)
{
throw new Exception(e);
}
}
public static <T> T unmarshal(InputStream is, Class<T> valueType) throws Exception
{
try
{
return objectMapper.readValue(is, valueType);
}
catch (JsonParseException e)
{
throw new Exception(e);
}
catch (JsonMappingException e)
{
throw new Exception(e);
}
catch (IOException e)
{
throw new Exception(e);
}
}
public static <T> T unmarshal(String str, Class<T> valueType) throws Exception
{
try
{
return objectMapper.readValue(str, valueType);
}
catch (JsonParseException e)
{
throw new Exception(e);
}
catch (JsonMappingException e)
{
throw new Exception(e);
}
catch (IOException e)
{
throw new Exception(e);
}
}
public static <T> T unmarshal(byte[] bytes, Class<T> valueType) throws Exception
{
try
{
if (bytes == null)
{
bytes = new byte[0];
}
return objectMapper.readValue(bytes, 0, bytes.length, valueType);
}
catch (JsonParseException e)
{
throw new Exception(e);
}
catch (JsonMappingException e)
{
throw new Exception(e);
}
catch (IOException e)
{
throw new Exception(e);
}
}
}
5、编写请求层
package com.example.repeat.controller;
import com.example.repeat.annotation.RepeatSubmit;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
public class UserController {
@RepeatSubmit
@RequestMapping("/hello")
public String hello(){
return "hello";
}
}
6、运行结果
这边使用了get请求,为了图方便,在浏览器不断请求,后台输出如下!
三、Gitee源码
SpringBoot使用Session防止表单重复提交: 在日常开发中,客户可能会存在反复点击提交按钮导致表单的重复提交,这个问题也是非常需要重视的,在本篇博客中,采用的是session、自定义注解和拦截器的方式来防止重复表单的重复提交,提高整体代码的优雅和整洁度!