勿以恶小而为之,勿以善小而不为--------------------------刘备

劝诸君,多行善事积福报,莫作恶

上一章简单介绍了Struts2编写自定义验证拦截敏感词汇(十二),如果没有看过,请观看上一章

一.为什么要进行表单验证

在实际开发和生活中,当我们在一个表单填写好数据,进行提交时,如果这个时候网卡,我们一般会再次,甚至是多次点击提交按钮(以为这样会快),也有的会先将浏览的页面返回到上一步,再进行退回,或者重复点击回车, 这样会导致数据重复提交。

为了防止表单重复提交,可以在客户端进行简单的处理,可以在服务器端进行处理。

这里只说明在服务器端进行的处理。

如果想了解客户端处理的,可以观看孤傲苍狼前辈的博客

二 搭建Struts2的基本运行环境,演示重复提交

二.一 创建UserAction

里面有常用的五个方法。

package com.yjl.web.action;
import org.apache.log4j.Logger;
import com.opensymphony.xwork2.ActionSupport;
/**
* @author 两个蝴蝶飞
* @version 创建时间:2018年9月14日 下午6:35:10
* 演示重复提交的Action.在添加用户是进行演示
*/
public class UserAction extends ActionSupport {
private static Logger logger=Logger.getLogger(UserAction.class);
private static final long serialVersionUID = -4164832837385401186L;
public String toAddUI(){
logger.info("跳转到添加学生的页面");
return "addUI";
}
public String add(){
logger.info("执行添加学生的操作");
return "toList";
}
public String toEditUI(){
logger.info("跳转到添加学生的页面");
return "editUI";
}
public String edit(){
logger.info("执行修改学生的操作");
return "toList";
}
public String delete(){
logger.info("执行删除学生的操作");
return "toList";
}
public String list(){
logger.info("执行查询学生的操作");
return "list";
}
}

二.二 配置struts.xml文件

<package name="user" namespace="/" extends="struts-default">
<action name="User_*" class="com.yjl.web.action.UserAction" method="{1}">
<result name="addUI">/WEB-INF/content/add.jsp</result>
<result name="editUI">/WEB-INF/content/edit.jsp</result>
<result name="list">/WEB-INF/content/list.jsp</result>
<!-- 应该当添加成功之后,就跳转到List界面 -->
<result name="toList" type="chain">User_list</result>
</action>
</package>

二.三 编写前端页面

二.三.一 编写 /content/add.jsp 页面

<body>
<h3>这是一个添加学生的页面</h3>
<s:form action="User_add" method="post" namespace="/">
<s:textfield label="姓名" name="name"></s:textfield>
<s:submit value="添加学生"/>
</s:form>
</body>

二.三.二 编写 /content/edit.jsp 页面

<body>
<h3>这是一个修改学生的页面</h3>
<s:form action="User_edit" method="post" namespace="/">
<s:textfield label="姓名" name="name"></s:textfield>
<s:submit value="添加学生"/>
</s:form>
</body>

二.三.三 编写 /content/list.jsp 页面

<body>
<h3>这是一个显示学生的页面</h3>
</body>

二.四 重启服务器,运行程序

输入网址: http://localhost:8080/Struts_Token/User_toAddUI

Struts2使用Token避免表单重复提交(十三)_token避免表单重复提交

当输入名字之后 ,

点击添加学生之后:

日志栏:


Struts2使用Token避免表单重复提交(十三)_token避免表单重复提交_02


页面上会显示:


Struts2使用Token避免表单重复提交(十三)_token避免表单重复提交_03


下面会显示学生的信息,

注意此时上面的地址栏是:User_add.action。

二.五 出现的问题

  1. 此时如果用户将光标定位到地址栏, 继续点击回车的话,还会继续执行一遍add()的方法:

Struts2使用Token避免表单重复提交(十三)_服务器_04

2.如果用户刷新浏览器的话,也会继续执行一遍add()的方法:

Struts2使用Token避免表单重复提交(十三)_token避免表单重复提交_05

会发现根本原因主要是地址栏的原因。地址栏的地址仍然是 User_add.action, 而不是 User_list.action

需要在 struts.xml中配置时type类型的值。

二.六 将类型type改成redirectAction

将toList 的返回类型,由默认的 chain改成redirectAction。

<!--错误的用法:<result name="toList" type="chain">User_list</result>-->
<result name="toList" type="redirectAction">User_list</result>

二.七 修改后重新验证

点击添加学生按钮:

Struts2使用Token避免表单重复提交(十三)_服务器_06

再次点击回车按钮时:

Struts2使用Token避免表单重复提交(十三)_tokenSession的使用_07

刷新浏览器时:

Struts2使用Token避免表单重复提交(十三)_token避免表单重复提交_08

都只是执行list()的方法,并不会再次执行add()的方法。

三. 方法运行时间过长导致的问题

你以为只是改成一个type值就万事大吉了吗?你太天真了。

如果添加add()这个方法运行的时候够长的话,仍然会有一些错误的。

实际情况中,add()这个方法会执行很多的逻辑验证,并不是只改变一个表,所以运行时间可能会长。

用Thread线程的睡眠来模拟这种情况。

三.一 在 add() 方法中,添加延迟处理

在add()方法中添加一个休眠的处理:

public String add(){
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info("执行添加学生的操作");
return "toList";
}

三.二 重启服务器,再次验证

重启服务器,进行相应的验证:

点击一次添加后,浏览器在转圈,用户非常有可能再点一次,那么就会产生这种情况:

Struts2使用Token避免表单重复提交(十三)_struts_09

很明显,执行了两次添加学生的操作。 这样,在展示数据的时候,就会展示两条, 而用户明明只添加了一条。

这是非常不可行的。

以前由于添加时时间短,没有发现这个问题。现在发现了,必须要去除。

Struts2框架提供了拦截器,来避免这一点

四. 利用 tokenSession 拦截器 防止表单提交

这种方法是在用户要提交的表单中,加入一个<s:token>标签,这样,当浏览器第一次访问这个带有<s:token>标签的页面时,

在服务器中,解析<s:token>标签的类(TokenTag.class),会生成一个随机的字符串(这个字符串,查看网页的源代码可以看到),

并且发送给客户端的浏览器,同时,在服务器中,会把这个随机字符串保存到用户的session对象中。

当第一次提交表单时,在服务器中,会比较客户端和服务器中分别保存的这个随机字符串,因为是第一次提交,所以这两个字符串相等,

然后进行正常的业务处理。第一次提交后,在服务器中的session中保存的这个随机字符串,会改变为其他的随机值,注意,这是很重要的一步!

此时,地址栏停留在处理用户提交数据的Action中,客户端中保存的随机字符串没有改变,若是刷新页面,

即重复提交,服务器再进行两个字符串的比较,会不相等,就会跳转到name为invalid.token的结果页面中,这样就会防止表单重复提交了。

(摘录于hackerain前辈的博客:javascript:void(0))

四.一 在 add.jsp 表单中添加<s:token/>

在add.jsp页面添加<s:token></s:token>

<body>
<h3>这是一个添加学生的页面</h3>
<s:form actinotallow="User_add" method="post" namespace="/">
<!-- 添加一个token -->
<s:token></s:token>
<s:textfield label="姓名" name="name"></s:textfield>
<s:submit value="添加学生"/>
</s:form>
</body>

Struts2中已经实际上token的操作,将其转成了一个拦截器。在package包下引用这个拦截器即可。

四.二 配置struts.xml文件

<package name="user" namespace="/" extends="struts-default">
<action name="User_*" class="com.yjl.web.action.UserAction" method="{1}">
<interceptor-ref name="tokenSession">
<!-- token对哪些方法起作用 -->
<param name="includeMethods">add,edit,delete</param>
</interceptor-ref>
<interceptor-ref name="defaultStack"></interceptor-ref>

<result name="addUI">/WEB-INF/content/add.jsp</result>
<result name="editUI">/WEB-INF/content/edit.jsp</result>
<result name="list">/WEB-INF/content/list.jsp</result>
<!-- 应该当添加成功之后,就跳转到List界面 -->
<result name="toList" type="redirectAction">User_list</result>

</action>
</package>

四.三 重启服务器,进行验证Token

输入网址: http://localhost:8080/Struts_Token/User_list.action

输入姓名后, 正常点击一次添加学生的按钮:

Struts2使用Token避免表单重复提交(十三)_延迟表单验证_10

发现正常的跳转和使用。

点击两次或者多次添加学生的按钮时:

Struts2使用Token避免表单重复提交(十三)_token避免表单重复提交_11

添加学生的操作也只添加了一次。

进行修改的话,多次点击,也是只修改一次。

完成正常的逻辑和功能操作。

本章节的代码链接为:

链接:https://pan.baidu.com/s/1lo58hZkgq9K0uyyMZLnGmg 
提取码:09hl

谢谢您的观看!!!