口碑好的武进网站建设,网站开发合同 中英文,手机上哪个网站,开发商虚假宣传退房赔偿标准一、前言
由于网络原因#xff0c;用户操作有误#xff08;连续点击两次以上提交按钮#xff09;#xff0c;或者页面卡顿等原因#xff0c;可能会出现请求重复提交#xff0c;造成数据库保存多条重复数据。后端实现拦截器防重。 那么如何防止请求重复提交呢#xff1f…一、前言
由于网络原因用户操作有误连续点击两次以上提交按钮或者页面卡顿等原因可能会出现请求重复提交造成数据库保存多条重复数据。后端实现拦截器防重。 那么如何防止请求重复提交呢一般有两种解决方案 第一种前端处理在提交完成之后将按钮禁用。 第二种后端处理使用拦截器拦截。 交给前端解决判断多长时间内不能再次点击按钮或者点击之后禁用按钮当然聪明的小伙伴能够绕过前端验证因此推荐后端进行拦截处理。
二、实现思路
使用拦截器防止请求重复提交本文模仿若依防重给大家分享利用 AOP 切面在进入方法前拦截通过 Session 或 Redis 的 key-value 键值对存储指定 keyurl消息头 来拼成字符串组成 key使用 请求参数时间 封装 map 对象赋值 value当 key 不存在时则为新的请求若存在则对请求参数以及请求的间隔时间进行判断是否重复提交。
2.1、自定义注解防止表单重复提交
package com.dian.jiao.interceptor.annotation;import java.lang.annotation.*;/*** 自定义注解防止表单重复提交*/
Inherited
Target(ElementType.METHOD)
Retention(RetentionPolicy.RUNTIME)
Documented
public interface RepeatSubmit {/*** 间隔时间(ms)小于此时间视为重复提交*/public int interval() default 5000;/*** 提示消息*/public String message() default 不允许重复提交请稍候再试;
}2.2、构建包装器
package com.dian.jiao.interceptor.wrapper;import com.dian.jiao.util.ServletUtils;import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;/*** 构建可重复读取inputStream的request*/
public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper {public final String UTF8 UTF-8;private final byte[] body;public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException {super(request);request.setCharacterEncoding(UTF8);response.setCharacterEncoding(UTF8);body ServletUtils.getBodyString(request).getBytes(UTF8);}Overridepublic BufferedReader getReader() throws IOException {return new BufferedReader(new InputStreamReader(getInputStream()));}Overridepublic ServletInputStream getInputStream() throws IOException {final ByteArrayInputStream bais new ByteArrayInputStream(body);return new ServletInputStream() {Overridepublic int read() throws IOException {return bais.read();}Overridepublic int available() throws IOException {return body.length;}Overridepublic boolean isFinished() {return false;}Overridepublic boolean isReady() {return false;}Overridepublic void setReadListener(ReadListener readListener) {}};}
}2.3、防止重复提交拦截器
package com.dian.jiao.interceptor;import com.dian.jiao.interceptor.annotation.RepeatSubmit;
import com.dian.jiao.interceptor.wrapper.RepeatedlyRequestWrapper;
import com.dian.jiao.pojo.User;
import com.dian.jiao.util.CommonUtils;
import com.dian.jiao.util.ServletUtils;
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;/*** 防止重复提交拦截器*/
public class RepeatSubmitInterceptor implements HandlerInterceptor {Overridepublic 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)) {// 将弹框字符串渲染到客户端ServletUtils.alert(response, annotation.message());return false;}}}return true;}/*** 验证是否重复提交实现具体的防重复提交的规则*/SuppressWarnings(unchecked)private boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation) {HttpSession session request.getSession();User user (User) session.getAttribute(loginUser);String nowParams ;if (request instanceof RepeatedlyRequestWrapper) {RepeatedlyRequestWrapper repeatedlyRequest (RepeatedlyRequestWrapper) request;nowParams ServletUtils.getBodyString(repeatedlyRequest);}// body参数为空获取Parameter的数据if (nowParams null || .equals(nowParams.trim()) {nowParams CommonUtils.toJSONString(request.getParameterMap());}MapString, Object nowDataMap new HashMap(2);nowDataMap.put(CommonUtils.REPEAT_PARAMS, nowParams);nowDataMap.put(CommonUtils.REPEAT_TIME, System.currentTimeMillis());// 请求地址作为存放cache的key值String url request.getRequestURI();// 用户IDString submitKey user null ? ServletUtils.getIpAddr(request) : String.valueOf(user.getId());// 唯一标识指定key url 消息头String cacheRepeatKey CommonUtils.REPEAT_SUBMIT_KEY url submitKey;Object sessionObj session.getAttribute(cacheRepeatKey);if (sessionObj ! null) {MapString, Object sessionMap (MapString, Object) sessionObj;if (sessionMap.containsKey(url)) {MapString, Object preDataMap (MapString, Object) sessionMap.get(url);if (CommonUtils.compareParams(nowDataMap, preDataMap) CommonUtils.compareTime(nowDataMap, preDataMap, annotation.interval())) {return true;}}}MapString, Object cacheMap new HashMap(1);cacheMap.put(url, nowDataMap);session.setAttribute(cacheRepeatKey, cacheMap);return false;}
}2.4、客户端工具类
package com.dian.jiao.util;import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.nio.charset.StandardCharsets;/*** 客户端工具类*/
public class ServletUtils {/*** 获取body请求参数* param request 请求对象{link ServletRequest}* return String*/public static String getBodyString(ServletRequest request) {StringBuilder sb new StringBuilder();BufferedReader reader null;try (InputStream inputStream request.getInputStream()) {reader new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));String line ;while ((line reader.readLine()) ! null) {sb.append(line);}} catch (IOException e) {e.printStackTrace();} finally {if (reader ! null) {try {reader.close();} catch (IOException e) {e.printStackTrace();}}}return sb.toString();}/*** 将弹框字符串渲染到客户端** param response 渲染对象* param msg 待渲染的弹框字符串*/public static void alert(HttpServletResponse response, String msg) {try {response.reset();response.setHeader(Content-type, text/html;charsetUTF-8);response.setCharacterEncoding(utf-8);PrintWriter out response.getWriter();out.print(script);out.print(alert(\ msg \););out.print(history.back(););out.print(/script);} catch (IOException e) {e.printStackTrace();}}/*** 获取客户端IP* * param request 请求对象* return IP地址*/public static String getIpAddr(HttpServletRequest request) {if (request null) {return unknown;}String ip request.getHeader(x-forwarded-for);if (ip null || ip.length() 0 || unknown.equalsIgnoreCase(ip)) {ip request.getHeader(Proxy-Client-IP);}if (ip null || ip.length() 0 || unknown.equalsIgnoreCase(ip)) {ip request.getHeader(X-Forwarded-For);}if (ip null || ip.length() 0 || unknown.equalsIgnoreCase(ip)) {ip request.getHeader(WL-Proxy-Client-IP);}if (ip null || ip.length() 0 || unknown.equalsIgnoreCase(ip)) {ip request.getHeader(X-Real-IP);}if (ip null || ip.length() 0 || unknown.equalsIgnoreCase(ip)) {ip request.getRemoteAddr();}return 0:0:0:0:0:0:0:1.equals(ip) ? 127.0.0.1 : getMultistageReverseProxyIp(ip);}/*** 从多级反向代理中获得第一个非unknown IP地址** param ip 获得的IP地址* return 第一个非unknown IP地址*/public static String getMultistageReverseProxyIp(String ip) {// 多级反向代理检测if (ip ! null ip.indexOf(,) 0) {final String[] ips ip.trim().split(,);for (String subIp : ips) {if (false isUnknown(subIp)) {ip subIp;break;}}}return StringUtils.substring(ip, 0, 255);}/*** 检测给定字符串是否为未知多用于检测HTTP请求相关** param checkString 被检测的字符串* return 是否未知*/public static boolean isUnknown(String checkString) {return StringUtils.isBlank(checkString) || unknown.equalsIgnoreCase(checkString);}
}2.5、公共工具类
package com.dian.jiao.util;import com.fasterxml.jackson.databind.ObjectMapper;import java.util.Map;public class CommonUtils {public static final String REPEAT_PARAMS repeatParams;public static final String REPEAT_TIME repeatTime;public static final String REPEAT_SUBMIT_KEY repeat_submit:;/*** 判断参数是否相同*/public static boolean compareParams(MapString, Object nowMap, MapString, Object preMap) {String nowParams (String) nowMap.get(REPEAT_PARAMS);String preParams (String) preMap.get(REPEAT_PARAMS);return nowParams.equals(preParams);}/*** 判断两次间隔时间*/public static boolean compareTime(MapString, Object nowMap, MapString, 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;}public static String toJSONString(Object object) {if (object ! null) {try {return new ObjectMapper().writeValueAsString(object);} catch (Exception e) {e.printStackTrace();}}return null;}
}
2.6、配置springmvc-servlet.xml添加拦截器
mvc:interceptorsmvc:interceptormvc:mapping path/**/!-- 配置不拦截的请求 --mvc:exclude-mapping path/login/mvc:exclude-mapping path/getCode/bean classcom.dian.jiao.interceptor.RepeatSubmitInterceptor//mvc:interceptor
/mvc:interceptors三、使用教程
在接口方法上添加 RepeatSubmit 注解即可注解参数说明
参数类型默认值描述intervalint5000间隔时间(ms)小于此时间视为重复提交messageString不允许重复提交请稍后再试提示消息
示例1采用默认参数
RepeatSubmit
public AjaxResult addSave()
{return AjaxResult.success();
}示例2指定防重复时间和错误消息
RepeatSubmit(interval 3000, message 您已经报名不能重复报名)
public AjaxResult addSave()
{return AjaxResult.success();
}大功告成