0.效果使用如图:
1.RepeatSubmit 注解
import java.lang.annotation.Documented;
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)
@Documented
public @interface RepeatSubmit
{
/**
* 间隔时间(ms),小于此时间视为重复提交
*/
public int interval() default 1000;
/**
* 提示消息
*/
public String message() default "不允许重复提交,请稍后再试";
}
2.RepeatSubmitInterceptor 拦截器
/**
* @Description 防止重复提交拦截器
*/
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import com.shinkeer.core.common.Result;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
/**
* 防止重复提交拦截器
*/
@Component
public class RepeatSubmitInterceptor implements HandlerInterceptor {
@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)) {
//如果本次提交被认为是重复提交,则在此处做具体的逻辑处理
//如:弹出警告窗口等
//Result 统一返回类,自己用的就好
Result result = Result.error(annotation.message());
ServletUtils.renderString(response, JSON.marshal(result));
return false;
}
}
return true;
} else {
return true;
}
}
/**
* 常量
*/
public final String REPEAT_PARAMS = "repeatParams";
public final String REPEAT_TIME = "repeatTime";
public final String SESSION_REPEAT_KEY = "repeatData";
/**
* 验证是否重复提交由子类实现具体的防重复提交的规则
* 判断请求url和数据是否和上一次相同
*
* @param request 请求对象
* @param annotation 防复注解
* @return 结果
*/
@SuppressWarnings("unchecked")
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;
}
}
3. JSON 自定义JSON
package com.shinkeer.core.repeatSubmit;
/**
* @Description 自定义 Json 工具类
*/
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
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;
/**
* JSON解析处理
*/
public class JSON
{
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);
}
}
}
4.WebMvcConfiguration 拦截器配置
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.shinkeer.core.config.interceptor.AuthLogInterceptor;
import com.shinkeer.core.repeatSubmit.RepeatSubmit;
import com.shinkeer.core.repeatSubmit.RepeatSubmitInterceptor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.trace.http.InMemoryHttpTraceRepository;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
/**
* Spring Boot 2.0 解决跨域问题
*/
@Configuration
public class WebMvcConfiguration extends WebMvcConfigurerAdapter implements WebMvcConfigurer {
@Value("${ignored}")
private List<String> ignored;
/**
* 日记拦截器
*/
@Bean
AuthLogInterceptor authLogInterceptor() {
return new AuthLogInterceptor();
}
/**
* 自定义注解防止表单重复提交 repeatSubmitInterceptor内置名字重复了 所以加了了Plus
*/
@Bean
RepeatSubmitInterceptor repeatSubmitInterceptorPlus() {
return new RepeatSubmitInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
//排除某些接口
List<String> businessLoginExcludePath = new ArrayList<>();
businessLoginExcludePath.add("/system/getValidateCode");
//swagger
businessLoginExcludePath.add("/druid/**");
businessLoginExcludePath.add("/swagger-ui.html");
businessLoginExcludePath.add("/swagger**/**");
businessLoginExcludePath.add("/webjars/**");
businessLoginExcludePath.add("/v2/**");
//静态资源
businessLoginExcludePath.add("/");
businessLoginExcludePath.add("/doc.html");
businessLoginExcludePath.add("/**/*.js");
businessLoginExcludePath.add("/**/*.css");
businessLoginExcludePath.add("/**/*.html");
businessLoginExcludePath.add("/**/*.svg");
businessLoginExcludePath.add("/**/*.pdf");
businessLoginExcludePath.add("/**/*.jpg");
businessLoginExcludePath.add("/**/*.png");
businessLoginExcludePath.add("/**/*.ico");
businessLoginExcludePath.add("/**/*.txt");
businessLoginExcludePath.add("/**/error");
registry.addInterceptor(authLogInterceptor()).addPathPatterns("/**").excludePathPatterns(businessLoginExcludePath);
registry.addInterceptor(repeatSubmitInterceptorPlus()).addPathPatterns("/**").excludePathPatterns(businessLoginExcludePath);
}
/**
* 方案一: 默认访问根路径跳转 doc.html页面 (swagger文档页面)
* 方案二: 访问根路径改成跳转 index.html页面 (简化部署方案: 可以把前端打包直接放到项目的 webapp,上面的配置)
*/
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("doc.html");
}
/**
* 添加Long转json精度丢失的配置
*
* @Return: void
*/
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
MappingJackson2HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
objectMapper.registerModule(simpleModule);
jackson2HttpMessageConverter.setObjectMapper(objectMapper);
jackson2HttpMessageConverter.setSupportedMediaTypes(getSupportedMediaTypes());
converters.add(jackson2HttpMessageConverter);
}
private List<MediaType> getSupportedMediaTypes() {
//创建消息转换器
List<MediaType> supportedMediaTypes = new ArrayList<>();
supportedMediaTypes.add(MediaType.ALL);
return supportedMediaTypes;
}
@Bean
public InMemoryHttpTraceRepository getInMemoryHttpTrace() {
return new InMemoryHttpTraceRepository();
}
/**
* 开启跨域 解决Springboot2.4+ “*”跨域问题
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
// 设置允许跨域的路由
registry.addMapping("/**")
// 设置允许跨域请求的域名
.allowedOriginPatterns("*")
// 是否允许证书(cookies)
.allowCredentials(true)
// 设置允许的方法
.allowedMethods("*")
// 跨域允许时间
.maxAge(3600);
}
}
5.Result 统一返回类
import com.shinkeer.card.enums.ExternalEnum;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.http.HttpStatus;
import java.util.Map;
/**
* @description: 统一返回类
*/
@Slf4j
public class Result<T> {
/**
* 成功标志
*/
@ApiModelProperty("成功标志")
private boolean success = true;
/**
* 返回处理消息
*/
@ApiModelProperty("返回处理消息")
private String message = "操作成功!";
/**
* 返回代码
*/
@ApiModelProperty("返回代码")
private Integer code = 0;
/**
* 返回数据对象 data
*/
@ApiModelProperty("返回数据对象")
private T result;
/**
* 时间戳
*/
@ApiModelProperty("时间戳")
private long timestamp = System.currentTimeMillis();
/**
* 请求日志跟踪ID
*/
@ApiModelProperty("请求日志跟踪ID")
private String traceId= MDC.get("TRACE_ID");
public boolean getSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public T getResult() {
return result;
}
public void setResult(T result) {
this.result = result;
}
public long getTimestamp() {
return timestamp;
}
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
public String getTraceId() {
return traceId;
}
public void setTraceId(String traceId) {
this.traceId = traceId;
}
public Result<T> success(String message) {
this.message = message;
this.code = HttpStatus.OK.value();
this.success = true;
return this;
}
public static<T> Result<T> OK() {
Result<T> r = new Result<T>();
r.setSuccess(true);
r.setCode(HttpStatus.OK.value());
r.setMessage("成功");
return r;
}
public static<T> Result<T> OK(ReturnCode returnCode) {
Result<T> r = new Result<T>();
r.setSuccess(true);
r.setCode(HttpStatus.OK.value());
r.setMessage(returnCode.getMsg());
r.setResult(null);
return r;
}
public static<T> Result<T> Error(ReturnCode returnCode) {
log.error(returnCode.getMsg());
Result<T> r = new Result<T>();
r.setSuccess(false);
r.setCode(returnCode.getCode());
r.setMessage(returnCode.getMsg());
r.setResult(null);
return r;
}
public static<T> Result<T> Error(ExternalEnum returnCode) {
Result<T> r = new Result<T>();
r.setSuccess(false);
r.setCode(returnCode.getCode());
r.setMessage(returnCode.getMsg());
r.setResult(null);
return r;
}
public static<T> Result<T> Error(Exception e) {
log.error(e.getMessage());
Result<T> r = new Result<T>();
r.setSuccess(false);
r.setCode(3000);
r.setMessage(e.getMessage());
r.setResult(null);
return r;
}
public static<T> Result<T> Error(ReturnCode returnCode,T data) {
log.error(returnCode.getMsg());
Result<T> r = new Result<T>();
r.setSuccess(false);
r.setCode(returnCode.getCode());
r.setMessage(returnCode.getMsg());
r.setResult(data);
return r;
}
public static<T> Result<T> Error(String msg) {
log.error(msg);
Result<T> r = new Result<T>();
r.setSuccess(false);
r.setCode(3000);
r.setMessage(msg);
r.setResult(null);
return r;
}
public static<T> Result<T> OK(T data) {
Result<T> r = new Result<T>();
r.setSuccess(true);
r.setCode(HttpStatus.OK.value());
r.setResult(data);
return r;
}
public static<T> Result<T> OK(Integer code,T data) {
Result<T> r = new Result<T>();
r.setSuccess(true);
r.setCode(code);
r.setResult(data);
return r;
}
public static<T> Result<T> OK(ExternalEnum returnCode) {
Result<T> r = new Result<T>();
r.setSuccess(true);
r.setCode(returnCode.getCode());
r.setMessage(returnCode.getMsg());
r.setResult(null);
return r;
}
public static<T> Result<T> OK(String msg, T data) {
Result<T> r = new Result<T>();
r.setSuccess(true);
r.setCode(HttpStatus.OK.value());
r.setMessage(msg);
r.setResult(data);
return r;
}
public static Result<Object> error(String msg) {
log.error(msg);
return error(HttpStatus.EXPECTATION_FAILED.value(), msg);
}
public static Result<Object> error(ReturnCode returnCode) {
log.error(returnCode.getMsg());
Result<Object> r = new Result<Object>();
r.setCode(returnCode.getCode());
r.setMessage(returnCode.getMsg());
r.setSuccess(false);
return r;
}
public static Result<Object> error(int code, String msg) {
log.error(msg);
Result<Object> r = new Result<Object>();
r.setCode(code);
r.setMessage(msg);
r.setSuccess(false);
return r;
}
public static Result<Object> error(Integer code, String msg) {
log.error(msg);
Result<Object> r = new Result<Object>();
r.setCode(code);
r.setMessage(msg);
r.setSuccess(false);
return r;
}
public static Result<Object> error(int code, String msg, Object data) {
log.error(msg);
Result<Object> r = new Result<Object>();
r.setCode(code);
r.setMessage(msg);
r.setSuccess(false);
r.setResult(data);
return r;
}
public Result<T> error500(String message) {
log.error(message);
this.message = message;
this.code = HttpStatus.EXPECTATION_FAILED.value();
this.success = false;
return this;
}
/**
* 无权限访问返回结果
*/
public static Result<Object> noauth(String msg) {
return error(HttpStatus.NON_AUTHORITATIVE_INFORMATION.value(), msg);
}
}
参考两位博主的帖子:
(51条消息) Spring Boot 使用自定义注解实现防止表单重复提交_springboot防重复提交_无敌牛牛的博客-CSDN博客
(51条消息) Spring Boot使用@RepeatSubmit 防止重复提交_缌唸的博客-CSDN博客_@repeatsubmit
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/192770.html