SSM框架防止重复请求

源:http://www.bj9420.com

编者: wRitchie(吴理琪)

SSM框架,即Spring+Spring MVC+Mybatis,在实践中由于网络延迟或多次点击提交等原因产生一个请求发送多次的情况,实际上,第一个请求才是客户端发送的,后面的请求对客户端并无意义。比如,客户端发送了三次保存的请求,实际上,客户端只想保存一条记录,而服务端却保存了三条,这导致重复数据,并且这些数据会对系统造成不必要的影响。另一种典型场景,用户提现,如重复提交,会导致多次提现,会带来巨大的经济损失,为了防止此类情况发生,需解决重复提交表单,解决方式思路,验证同一个URL及数据参数是否相同,如相同,则重复提交,否则,正常提交。

一、 SSM框架后端解决

1、 自定义防止重复的注解

1.packagecom.bj9420.framework.interceptor;
2.
3.importjava.lang.annotation.ElementType;
4.importjava.lang.annotation.Retention;
5.importjava.lang.annotation.RetentionPolicy;
6.importjava.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@interfacePreventRepeat {
19. }

2、 自定义拦截器

1.packagecn.airbest.framework.interceptor;
2.
3.importcn.airbest.constants.SystemConstant;
4.importcn.airbest.model.Result;
5.importcom.alibaba.fastjson.JSON;
6.importcom.fasterxml.jackson.databind.ObjectMapper;
7.importlombok.extern.slf4j.Slf4j;
8.importorg.springframework.web.method.HandlerMethod;
9.importorg.springframework.web.servlet.handler.HandlerInterceptorAdapter;
10.
11.importjavax.servlet.http.HttpServletRequest;
12.importjavax.servlet.http.HttpServletResponse;
13.importjava.io.PrintWriter;
14.importjava.lang.reflect.Method;
15.importjava.util.HashMap;
16.importjava.util.Map;
17.
18. /**
19. * @author :wRitchie
20. * @Description :相同url和数据拦截器 为了防止重复提交等操作
21. * 继承拦截器适配器
22. * @Title :SameUrlDataInterceptor.java
23. * @date 2020/12/7 13:47
24. * @Version :V1.0
25. * @Copyright (c):http://wwww.bj9420.com 2020 All rights reserved.
26. */
27. @Slf4j
28.publicclassRepeatUrlDataInterceptorextendsHandlerInterceptorAdapter {
29. /**
30. * @Author: wRitchie
31. * @Description: preHandle 覆盖父类的preHandle方法
32. * 预处理回调方法,实现处理器的预处理,验证是否为重复提交,第三个参数为响应的处理器,自定义Controller
33. * 返回值:true表示继续流程(如调用下一个拦截器或处理器);false表示流程中断(如登录检查失败),不会继续调用其他的拦截器或处理器,此时我们需要通过response来产生响应;
34. * @Param: [request, response, handler]
35. * @return: boolean
36. * @Date: 2020/12/7 13:54
37. */
38. @Override
39.publicbooleanpreHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throwsException {
40. // 1、判断handler参数是否为HandlerMethod类的实例
41.if(handlerinstanceofHandlerMethod) {
42. // 2、获取方法注解查看方式是否有PreventRepeat注解
43. HandlerMethod handlerMethod = (HandlerMethod) handler;
44. Method method = handlerMethod.getMethod();
45. PreventRepeat annotation = method.getAnnotation(PreventRepeat.class);
46.if(annotation !=null) {
47. // 3、 调用重复数据验证方法
48.booleanreturnValue = repeatDataValidator(request);
49.if(returnValue) {
50. //重复提交,返回JSON信息
51. response.setCharacterEncoding("UTF-8");
52. response.setContentType("application/json;charset=utf-8");
53. PrintWriter out=response.getWriter();
54. //PrintWriter out=new PrintWriter(response.getWriter());
55. Result result=newResult();
56. result.setMsgcode(SystemConstant.MSG_CODE_SUC_AGAIN);
57. result.setMessage("请不要重复提交。");
58. out.append(JSON.toJSONString(result) );
59. out.flush();
60. out.close();
61.
62.returnfalse;
63. }else{
64.returntrue;
65. }
66. }else{
67.returntrue;
68. }
69. }else{
70. // 4. 如果参数不是HandlerMethod类的实例,则调用父类的preHandle方法
71.returnsuper.preHandle(request, response, handler);
72. }
73. }
74.
75.
76. /**
77. * @Author: wRitchie
78. * @Description: repeatDataValidator 验证请求的url+请求数据参数提交是否相同
79. * 相同返回true,表示重复请求;否则返回false,表示不是重复提交
80. * @Param: [httpServletRequest]
81. * @return: boolean
82. * @Date: 2020/12/7 13:53
83. */
84.publicbooleanrepeatDataValidator(HttpServletRequest httpServletRequest) {
85.try{
86. // 1、将url+请求数据参数转换为json字符串,需pom内引用jackson-databind
87. ObjectMapper objectMapper =newObjectMapper();
88. String params = objectMapper.writeValueAsString(httpServletRequest.getParameterMap());
89. // 2、 获取当前请求的url地址,以url为key,请求数据参数为值存在map里
90. String url = httpServletRequest.getRequestURI();
91. Map map =newHashMap(4);
92. map.put(url, params);
93. String curUrlData = map.toString();
94. log.info("**** key url:"+url);
95. log.info("**** value curUrlData:"+curUrlData);
96. // 3、从session中获取上一次请求存储的url和请求数据参数字符串
97. Object preUrlData = httpServletRequest.getSession().getAttribute("preUrlData");
98. // 4、若上次请求的url+数据为null,则表示尚未访问的页面,将当前请求的url+请求数据参数存储到session中
99.if(preUrlData ==null) {
100. httpServletRequest.getSession().setAttribute("preUrlData", curUrlData);
101.returnfalse;
102. }else{
103. // 5、若上次访问的url+请求数据参数与本次url+请求数据参数相同,则表示重复提交
104.if(preUrlData.toString().equals(curUrlData)) {
105.returntrue;
106. }else{
107. //6、若上次访问的url+请求数据参数和本次url+请求数据参数不同,则不是重复提交
108. httpServletRequest.getSession().setAttribute("preUrlData", curUrlData);
109.returnfalse;
110. }
111. }
112. }catch(Exception e) {
113. log.info("验证是否为重复请求异常。");
114.returnfalse;
115. }
116. }
117. }

3、 Spring MVC 配置文件中添加配置

1.
2.
3.
4.
5.
6.
7.
8.
9.

4、 使用方法,在Controller类的方法上,增加@PreventRepeat注解


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.publicResult> withdraw(String type,String userId,String account,String payType) {
12. //提现代码
13. ......
14.
15. }

5、 运行结果

这样,以拦截器的方式,SSM框架完美实现防止重复请求。