SSM框架防止重复请求
编者: wRitchie(吴理琪) 来源:http://www.bj9420.com
SSM框架,即Spring+Spring MVC+Mybatis,在实践中由于网络延迟或多次点击提交等原因产生一个请求发送多次的情况,实际上,第一个请求才是客户端发送的,后面的请求对客户端并无意义。比如,客户端发送了三次保存的请求,实际上,客户端只想保存一条记录,而服务端却保存了三条,这导致重复数据,并且这些数据会对系统造成不必要的影响。另一种典型场景,用户提现,如重复提交,会导致多次提现,会带来巨大的经济损失,为了防止此类情况发生,需解决重复提交表单,解决方式思路,验证同一个URL及数据参数是否相同,如相同,则重复提交,否则,正常提交。
SSM框架后端解决
1、 自定义防止重复的注解
1. package com.bj9420.framework.interceptor; 2. 3. import java.lang.annotation.ElementType; 4. import java.lang.annotation.Retention; 5. import java.lang.annotation.RetentionPolicy; 6. import java.lang.annotation.Target; 7. 8. /** 9. * @author :wRitchie 10. * @Description :自定义防止重复的注解 11. * @Title :PreventRepeat.java 12. * @date 2020/12/7 13:45 13. * @Version :V1.0 14. * @Copyright (c):http://www.bj9420.com 2020 All rights reserved. 15. */ 16. @Target(ElementType.METHOD) 17. @Retention(RetentionPolicy.RUNTIME) 18. public @interface PreventRepeat { 19. }
2、 自定义拦截器
1. package com.bj9420.framework.interceptor; 2. 3. import com.fasterxml.jackson.databind.ObjectMapper; 4. import lombok.extern.slf4j.Slf4j; 5. import org.springframework.web.method.HandlerMethod; 6. import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; 7. 8. import javax.servlet.http.HttpServletRequest; 9. import javax.servlet.http.HttpServletResponse; 10. import java.lang.reflect.Method; 11. import java.util.HashMap; 12. import java.util.Map; 13. 14. /** 15. * @author :wRitchie 16. * @Description :相同url和数据拦截器 为了防止重复提交等操作 17. * 继承拦截器适配器 18. * @Title :RepeatUrlDataInterceptor.java 19. * @date 2020/12/7 13:47 20. * @Version :V1.0 21. * @Copyright (c):http://www.bj9420.com 2020 All rights reserved. 22. */ 23. @Slf4j 24. public class RepeatUrlDataInterceptor extends HandlerInterceptorAdapter { 25. /** 26. * @Author: wRitchie 27. * @Description: preHandle 覆盖父类的preHandle方法 28. * 预处理回调方法,实现处理器的预处理,验证是否为重复提交,第三个参数为响应的处理器,自定义Controller 29. * 返回值:true表示继续流程(如调用下一个拦截器或处理器);false表示流程中断(如登录检查失败),不会继续调用其他的拦截器或处理器,此时我们需要通过response来产生响应; 30. * @Param: [request, response, handler] 31. * @return: boolean 32. * @Date: 2020/12/7 13:54 33. */ 34. @Override 35. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 36. // 1、判断handler参数是否为HandlerMethod类的实例 37. if (handler instanceof HandlerMethod) { 38. // 2、获取方法注解查看方式是否有PreventRepeat注解 39. HandlerMethod handlerMethod = (HandlerMethod) handler; 40. Method method = handlerMethod.getMethod(); 41. PreventRepeat annotation = method.getAnnotation(PreventRepeat.class); 42. if (annotation != null) { 43. // 3、 调用重复数据验证方法 44. boolean result = repeatDataValidator(request); 45. if (result) { 46. return false; 47. } else { 48. return true; 49. } 50. } else { 51. return true; 52. } 53. } else { 54. // 4. 如果参数不是HandlerMethod类的实例,则调用父类的preHandle方法 55. return super.preHandle(request, response, handler); 56. } 57. } 58. 59. 60. /** 61. * @Author: wRitchie 62. * @Description: repeatDataValidator 验证请求的url+请求数据参数提交是否相同 63. * 相同返回true,表示重复请求;否则返回false,表示不是重复提交 64. * @Param: [httpServletRequest] 65. * @return: boolean 66. * @Date: 2020/12/7 13:53 67. */ 68. public boolean repeatDataValidator(HttpServletRequest httpServletRequest) { 69. try { 70. // 1、将url+请求数据参数转换为json字符串,需pom内引用jackson-databind 71. ObjectMapper objectMapper = new ObjectMapper(); 72. String params = objectMapper.writeValueAsString(httpServletRequest.getParameterMap()); 73. // 2、 获取当前请求的url地址,以url为key,请求数据参数为值存在map里 74. String url = httpServletRequest.getRequestURI(); 75. Map map = new HashMap(4); 76. map.put(url, params); 77. String curUrlData = map.toString(); 78. log.info("**** curUrlData:"+curUrlData); 79. // 3、从session中获取上一次请求存储的url和请求数据参数字符串 80. Object preUrlData = httpServletRequest.getSession().getAttribute("preUrlData"); 81. // 4、若上次请求的url+数据为null,则表示尚未访问的页面,将当前请求的url+请求数据参数存储到session中 82. if (preUrlData == null) { 83. httpServletRequest.getSession().setAttribute("preUrlData", curUrlData); 84. return false; 85. } else { 86. // 5、若上次访问的url+请求数据参数与本次url+请求数据参数相同,则表示重复提交 87. if (preUrlData.toString().equals(curUrlData)) { 88. return true; 89. } else { 90. //6、若上次访问的url+请求数据参数和本次url+请求数据参数不同,则不是重复提交 91. httpServletRequest.getSession().setAttribute("preUrlData", curUrlData); 92. return false; 93. } 94. } 95. } catch (Exception e) { 96. log.info("验证是否为重复请求异常。"); 97. return false; 98. } 99. } 100. }
3、 Spring MVC 配置文件中添加配置
1. 2. 3. 4. 5. 6. 7. 8. 9.
4、 使用方法,在Controller类的方法上,增加@PreventRepeat注解
1. /** 2. * @Author: wRitchie 3. * @Description: withdraw 提现 4. * @Param: [type, userId,account,payType] 5. * @return: com.bj9420.model.Result> 6. * @Date: 2020-12-07 15:15 7. */ 8. @RequestMapping("/withdraw") 9. @ResponseBody 10. @PreventRepeat 11. public Result> withdraw(String type,String userId,String account,String payType) { 12. //提现代码 13. ...... 14. 15. }
这样,基本实现了防止重复请求,但拦截器的方式需要优化的是将拦截重复请求的返回给请求端,目前的方式,只是不处理,没有返回信息。