具体的做法:
1、获取用户填写用户名和密码的页面时向后台发送一次请求,这时后台会生成唯一的随机标识号,专业术语称为Token(令牌)。
2、将Token发送到客户端的Form表单中,在Form表单中使用隐藏域来存储这个Token,表单提交的时候连同这个Token一起提交到服务器端。
3、服务器端判断客户端提交上来的Token与服务器端生成的Token是否一致,如果不一致,那就是重复提交了,此时服务器端就可以不处理重复提交的表单。如果相同则处理表单提交,处理完后清除当前用户的Session域中存储的标识号。
看具体的范例:
1.创建FormServlet,用于生成Token(令牌)和跳转到form.jsp页面
importjava.io.IOException;importjavax.servlet.ServletException;importjavax.servlet.http.HttpServlet;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;public class FormServlet extendsHttpServlet {private static final long serialVersionUID = -884689940866074733L;public voiddoGet(HttpServletRequest request, HttpServletResponse response)throwsServletException, IOException {
String token= UUID.randomUUID().toString() ;//创建令牌
System.out.println("在FormServlet中生成的token:"+token);
request.getSession().setAttribute("token", token); //在服务器使用session保存token(令牌)
request.getRequestDispatcher("/form.jsp").forward(request, response);//跳转到form.jsp页面
}public voiddoPost(HttpServletRequest request, HttpServletResponse response)throwsServletException, IOException {
doGet(request, response);
}
}
2.在form.jsp中使用隐藏域来存储Token(令牌)
form表单">
--%>
用户名:
3.DoFormServlet处理表单提交
importjava.io.IOException;importjavax.servlet.ServletException;importjavax.servlet.http.HttpServlet;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;public class DoFormServlet extendsHttpServlet {public voiddoGet(HttpServletRequest request, HttpServletResponse response)throwsServletException, IOException {boolean b = isRepeatSubmit(request);//判断用户是否是重复提交
if(b==true){
System.out.println("请不要重复提交");return;
}
request.getSession().removeAttribute("token");//移除session中的token
System.out.println("处理用户提交请求!!");
}/*** 判断客户端提交上来的令牌和服务器端生成的令牌是否一致
*@paramrequest
*@return* true 用户重复提交了表单
* false 用户没有重复提交表单*/
private booleanisRepeatSubmit(HttpServletRequest request) {
String client_token= request.getParameter("token");//1、如果用户提交的表单数据中没有token,则用户是重复提交了表单
if(client_token==null){return true;
}//取出存储在Session中的token
String server_token = (String) request.getSession().getAttribute("token");//2、如果当前用户的Session中不存在Token(令牌),则用户是重复提交了表单
if(server_token==null){return true;
}//3、存储在Session中的Token(令牌)与表单提交的Token(令牌)不同,则用户是重复提交了表单
if(!client_token.equals(server_token)){return true;
}return false;
}public voiddoPost(HttpServletRequest request, HttpServletResponse response)throwsServletException, IOException {
doGet(request, response);
}
}
方案二:判断请求url和数据是否和上一次相同
推荐,非常简单,页面不需要任何传入,只需要在验证的controller方法上写上自定义注解即可
写好自定义注解
importjava.lang.annotation.ElementType;importjava.lang.annotation.Retention;importjava.lang.annotation.RetentionPolicy;importjava.lang.annotation.Target;/*** 一个用户 相同url 同时提交 相同数据 验证
*@authorAdministrator
**/@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)public @interfaceSameUrlData {
}
写好拦截器
importjava.lang.reflect.Method;importjava.util.HashMap;importjava.util.Map;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importorg.springframework.web.method.HandlerMethod;importorg.springframework.web.servlet.handler.HandlerInterceptorAdapter;importcom.thinkgem.jeesite.common.mapper.JsonMapper;/*** 一个用户 相同url 同时提交 相同数据 验证
* 主要通过 session中保存到的url 和 请求参数。如果和上次相同,则是重复提交表单
*@authorAdministrator
**/
public class SameUrlDataInterceptor extendsHandlerInterceptorAdapter{
@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throwsException {if (handler instanceofHandlerMethod) {
HandlerMethod handlerMethod=(HandlerMethod) handler;
Method method=handlerMethod.getMethod();
SameUrlData annotation= method.getAnnotation(SameUrlData.class);if (annotation != null) {if(repeatDataValidator(request))//如果重复相同数据
return false;else
return true;
}return true;
}else{return super.preHandle(request, response, handler);
}
}/*** 验证同一个url数据是否相同提交 ,相同返回true
*@paramhttpServletRequest
*@return
*/
public booleanrepeatDataValidator(HttpServletRequest httpServletRequest)
{
String params=JsonMapper.toJsonString(httpServletRequest.getParameterMap());
String url=httpServletRequest.getRequestURI();
Map map=new HashMap();
map.put(url, params);
String nowUrlParams=map.toString();//
Object preUrlParams=httpServletRequest.getSession().getAttribute("repeatData");if(preUrlParams==null)//如果上一个数据为null,表示还没有访问页面
{
httpServletRequest.getSession().setAttribute("repeatData", nowUrlParams);return false;
}else//否则,已经访问过页面
{if(preUrlParams.toString().equals(nowUrlParams))//如果上次url+数据和本次url+数据相同,则表示城府添加数据
{return true;
}else//如果上次 url+数据 和本次url加数据不同,则不是重复提交
{
httpServletRequest.getSession().setAttribute("repeatData", nowUrlParams);return false;
}
}
}
}
方案三:利用Spring AOP和redis的锁来实现防止表单重复提交
主要是利用了redis的分布式锁机制
1、注解:
importjava.lang.annotation.ElementType;importjava.lang.annotation.Retention;importjava.lang.annotation.RetentionPolicy;importjava.lang.annotation.Target;/*** 防止重复提交注解
*@authorzzp 2018.03.11
*@version1.0*/@Retention(RetentionPolicy.RUNTIME)//在运行时可以获取
@Target(value = {ElementType.METHOD, ElementType.TYPE}) //作用到类,方法,接口上等
public @interfacePreventRepetitionAnnotation {
}
2、AOP代码
importjava.lang.reflect.Method;importjava.util.UUID;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpSession;importorg.aspectj.lang.ProceedingJoinPoint;importorg.aspectj.lang.annotation.Around;importorg.aspectj.lang.annotation.Aspect;importorg.aspectj.lang.reflect.MethodSignature;importorg.com.rlid.utils.json.JsonBuilder;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.context.annotation.EnableAspectJAutoProxy;importorg.springframework.stereotype.Component;importdemo.zzp.app.aop.annotation.OperaterAnnotation;importdemo.zzp.app.redis.JedisUtils;/*** 防止重复提交操作AOP类
*@authorzzp 2018.03.10
*@version1.0*/@Aspect
@Component
@EnableAspectJAutoProxy(proxyTargetClass=true)public classPreventRepetitionAspect {
@AutowiredprivateJedisUtils jedisUtils;private static final String PARAM_TOKEN = "token";private static final String PARAM_TOKEN_FLAG = "tokenFlag";/*** around
*@throwsThrowable*/@Around(value= "@annotation(demo.zzp.app.aop.annotation.PreventRepetitionAnnotation)")public Object excute(ProceedingJoinPoint joinPoint) throwsThrowable{try{
Object result= null;
Object[] args=joinPoint.getArgs();for(int i = 0;i < args.length;i++){if(args[i] != null && args[i] instanceofHttpServletRequest){
HttpServletRequest request= (HttpServletRequest) args[i];//被调用的方法需要加上HttpServletRequest request这个参数
HttpSession session =request.getSession();if(request.getMethod().equalsIgnoreCase("get")){//方法为get
result =generate(joinPoint, request, session, PARAM_TOKEN_FLAG);
}else{//方法为post
result =validation(joinPoint, request, session, PARAM_TOKEN_FLAG);
}
}
}returnresult;
}catch(Exception e) {
e.printStackTrace();return JsonBuilder.toJson(false, "操作失败!", "执行防止重复提交功能AOP失败,原因:" +e.getMessage());
}
}public Object generate(ProceedingJoinPoint joinPoint, HttpServletRequest request, HttpSession session,String tokenFlag) throwsThrowable {
String uuid=UUID.randomUUID().toString();
request.setAttribute(PARAM_TOKEN, uuid);returnjoinPoint.proceed();
}public Object validation(ProceedingJoinPoint joinPoint, HttpServletRequest request, HttpSession session,String tokenFlag) throwsThrowable {
String requestFlag=request.getParameter(PARAM_TOKEN);//redis加锁
boolean lock = jedisUtils.tryGetDistributedLock(tokenFlag + requestFlag, requestFlag, 60000);if(lock){//加锁成功//执行方法
Object funcResult =joinPoint.proceed();//方法执行完之后进行解锁
jedisUtils.releaseDistributedLock(tokenFlag +requestFlag, requestFlag);returnfuncResult;
}else{//锁已存在
return JsonBuilder.toJson(false, "不能重复提交!", null);
}
}
}
3、Controller代码
@RequestMapping(value = "/index",method =RequestMethod.GET)
@PreventRepetitionAnnotationpublic String toIndex(HttpServletRequest request,Mapmap){return "form";
}
@RequestMapping(value= "/add",method =RequestMethod.POST)
@ResponseBody
@PreventRepetitionAnnotationpublicString add(HttpServletRequest request){try{
Thread.sleep(5000);
}catch(InterruptedException e) {
e.printStackTrace();
}return JsonBuilder.toJson(true, "保存成功!",null);
}
第一次点击提交表单,判断到当前的token还没有上锁,即给该token上锁。如果连续点击提交,则提示不能重复提交,当上锁的那次操作执行完,redis释放了锁之后才能继续提交。