日常开发中,表单的提交是无法避免的,而我们必须熟知的一点是“在做后退或刷新操作时,post会重新提交请求是有害的,而get虽然会重新获取数据但却是无害的”。所以我们要禁止用户重复提交表单。

首先,我们要知道在什么情况下表单会重复提交

  1. 点击提交按钮两次。
  2. 点击刷新按钮。
  3. 回退,然后重复操作。
  4. 进行一些恶意操作。

那么,如何防止表单重复提交呢?

其实很简单,根据数据流向的过程,可以从三个层面进行控制:

1、前端层面

用户点击按钮触发submit时,前端js控制提交按钮的状态,将按钮的disabled属性为true,防止重复点击。

//监听submit事件,触发后将提交按钮设置为不可用
$('form').submit(function() {$('button[type=submit]').attr('disabled', true);
});

基本上禁用按钮后,前端不用管了,因为数据如果通不过laravel的验证或发生异常,在laravel中的做法是back()->withInput()->withErrors();这时页面会刷新,按钮会自行恢复状态。

如果是ajax方式提交,直接在function里发送ajax请求前禁用就行了,然后根据请求的结果来恢复按钮的状态或跳转页面就可以了。

2、服务端层面

思路::

在显示表单页面时,服务端生成一个随机字符串并以该字符串为key保存在session中并将其回显在表单一个隐藏的input中,当提交表单时,服务端根据这个隐藏input的值(即session中的key)去session中取值,如果该key存在于session中表示正常提交,并立即从session中删除该key,若发生重复提交,session中的这个key已经被删除了,就可以给前端相应的提示“表单重复提交”。

缺点:

  1. 刷新界面,会导session中存放了多个key,数据冗余且存在漏洞,因为存在多个key即意味着同一时间可以使用不同key来提交同一份数据;

    【补充】laravel中可以通过flash方法来存储只在下个请求有效的session数据,即在下一请求之后,该数据会被自动从session中清除,这样确实能解决刷新界面后session中保存多个key的问题,但会带来一个新的问题,列举一个场景加以说明:假如某用户正在写评论,写到一半被旁边推荐的一篇文章吸引,就先去看文章了,等看完回来继续写完评论提交,会发生什么事?会被当做表单重复提交处理,因为查看文章时,已经将flash方式保存的session清空了。

  2. 不够简洁,要知道这里解决的问题是要防止表单重复提交,完全没有必要生成一个动态的类似token东西,针对某一类表单提交(如注册)将存储在session中的key固定就好了,这样就可以省去form中那个隐藏的input了。

优化后的思路:

针对不同类型的表单(这里定义登陆、注册为不同类型的表单)服务端维护多个不同的key(比如登陆表单在session中对应的key固定为‘login’,注册表单的key固定为’register’),在显示表单页面时将key保存进session(对应的value可以存1,也可以存当前时间,存当前时间的话,你可以根据在提交表彰时根据时间间隔来作进一步的控制),表单提交时将其删除,若出现重复提交,session中不存在这个key,你就可以提示用户“不要重复提交”了。

具体实现:

1、在controller中显示注册界面的方法里保存session

public function showRegistrationForm(Request $request)
{       $request->session()->put('register',time());return view('auth.register');
}

2、在处理表单提交方法中判断是否重复提交

public function register(Request $request)
{if($this->request->session()->has(‘register’)){//存在则表示是首次提交,清空session中的'register'$this->request->session()->forget(‘register’);}else{//否则抛http异常,跳转到403页面throw  new HttpException(403,'请忽重复注册');}//省略下面的验证、注册逻辑等代码
}

【补充】如果是参数验证失败,比如手机号已注册之类的,你back()->withInput()->withErrors();是会重新执行showRegistrationForm()方法的,所以出错后再次提交是不会被当做重复提交处理的

简单对其进行封装

<?php
namespace App\Http\Controllers;use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\Request;/*** 基础控制器,封装了web及api请求的一些公共方法* @author 94505**/
class Controller extends BaseController
{use AuthorizesRequests, DispatchesJobs, ValidatesRequests;/*** 请求** @var Request*/protected $request;public function __construct(){$this->request = app('request');}/*** 防止表单重复提交的key前缀* @var string*/private $formResubmitPrefix = 'f_';/*** 将key加个前缀* @param unknown $key* @return string*/private function formResubmitKeyProcess($key){if(empty($key)){//默认使用当前路由的uri为keyreturn $this->formResubmitPrefix.Route::current()->uri;}else{return $this->formResubmitPrefix.$key;}}/*** 在初始化表单前调用(如上面分步实现中的showRegistrationForm()方法中)* @param unknown $key*/protected function formInit($key = null){$key = $this->formResubmitKeyProcess($key);$this->request->session()->put($key,time());}/*** 在处理表单提交的方法中调用(如上面分步实现中的register()方法)* @param string $message* @param unknown $key* @throws HttpException*/protected function formSubmited(string $message = '请忽重复提交!',$key = null){$key = $this->formResubmitKeyProcess($key);if($this->request->session()->has($key)){$this->request->session()->forget($key);}else{throw  new HttpException(403,$message);}}        /*** 在处理表单提交的方法中调用(如上面分步实现中的register()方 法),该方法方便自定义重复提交时的提示页面,可以在子类中if判断一下,如果发生重复提交,响应自定义的界面* @param string $message* @param unknown $key*/protected function formSubmitIsRepetition(string $message = '请勿重复提交!',$key = null){$key = $this->formResubmitKeyProcess($key);if($this->request->session()->has($key)){$this->request->session()->forget($key);return false;}else{return response()->view('errors.403',['message'=>$message],403);}}/*** 该方法用于ajax请求,返回的数据是数组* @param string $message* @param unknown $key*/protected function formSubmitedForAjax(string $message = '请勿重复提交!',$key = null){$key = $this->formResubmitKeyProcess($key);if($this->request->session()->has($key)){$this->request->session()->forget($key);return false;}else{return ['result'=>'fail','message'=>$message];}}
}

在需要防止表单重复提交的控制器内,继承上面封装的Controller就可以直接调用里面的方法了,记得在子类构造方法中调用parent::__construct();,不然$this->request会为null,当然你也可以改成用全局Session辅助函数session()。

3、数据库层面

数据库加unique索引的话只能根据实际情况权衡决定。

比如用户表的手机号(列phone)可用来登陆,必须要求唯一,但在大多数情况下你无法加这个索引,因为现在一般都支持多种登陆方式,如微信登陆、微博登陆,这个手机号可能会没有值,除非程序自动生成一个,但是否有必要?再比如一个varchar类型的列,虽然数据是唯一的,也不会出现空的情况,考虑到varchar类型插入与修改数据时更新索引的性能消耗,可能会放弃加这个索引。

作为一个严谨的程序员,防止表单重复提交处理是必须的!

PHP防止表单重复提交相关推荐

  1. springboot 订单重复提交_Spring Boot (一) 校验表单重复提交

    一.前言 在某些情况下,由于网速慢,用户操作有误(连续点击两下提交按钮),页面卡顿等原因,可能会出现表单数据重复提交造成数据库保存多条重复数据. 存在如上问题可以交给前端解决,判断多长时间内不能再次点 ...

  2. SpringMVC中实现的token,防表单重复提交

    一:首先创建一个token处理类  ,这里的类名叫 TokenHandler private static Logger logger = Logger.getLogger(TokenHandler. ...

  3. 简单介绍redis分布式锁解决表单重复提交的问题

    在系统中,有些接口如果重复提交,可能会造成脏数据或者其他的严重的问题,所以我们一般会对与数据库有交互的接口进行重复处理.本文就详细的介绍一下redis分布式锁解决表单重复提交,感兴趣的可以了解一下 假 ...

  4. python表单防重复提交_防止表单重复提交的几种策略

    表单重复提交是在多用户Web应用中最常见.带来很多麻烦的一个问题.有很多的应用场景都会遇到重复提交问题,比如: 点击提交按钮两次. 点击刷新按钮. 使用浏览器后退按钮重复之前的操作,导致重复提交表单. ...

  5. 表单重复提交的解决方法

    表单重复提交的解决方法 参考文章: (1)表单重复提交的解决方法 (2)https://www.cnblogs.com/lwj-0923/p/7367517.html 备忘一下.

  6. 如何避免表单重复提交

    客户端方案 禁掉提交按钮. 表单提交后使用Javascript使提交按钮disable.这种方法防止心急的用户多次点击按钮.但有个问题,如果客户端把Javascript给禁止掉,这种方法就无效了. 使 ...

  7. ASP.NET防止按F5键造成表单重复提交

    F5键会引起表单重复提交,做过asp.net相信都会遇到过这个问题. 最有效的是一篇发表在MSDN的方法 原理如下: 在asp.net页面中有一个名为_VIEWSTATE的隐藏域,这个隐藏域保存着当前 ...

  8. 使用Struts2防止表单重复提交

    用户重复提交表单在某些场合将会造成非常严重的后果.例如,在使用信用卡进行在线支付的时候,如果服务器的响应速度太慢,用户有可能会多次点击提交按钮,而这可能导致那张信用卡上的金额被消费了多次.因此,重复提 ...

  9. Struts2防止表单重复提交

    最近开发中涉及到了表单重复提次的问题,通过研究做个总结. 防止表单重复提交主要用的到标签是<s: token />,拦截器 <interceptor-ref name="t ...

  10. 开发期间模板引擎页面修改以后,要实时生效 || 登陆成功,防止表单重复提交,可以重定向||只有登录之后才能访问相关的页面

    去除模板引擎的缓存 th:if  优先级高于  th:text 登陆成功,防止表单重复提交,可以重定向到主页 只有登录之后才能访问相关的页面 login.html <!DOCTYPE html& ...

最新文章

  1. C语言掉电保存的变量,求解释,怎么能让程序里的变量在关闭后依然保存呢?...
  2. hadoop和spark搭建记录
  3. 计算机网络 --- 网络层IP地址
  4. 190602每日一句
  5. Sigar介绍和配置
  6. 完成20亿元D轮融资,明略数据升级为明略科技集团
  7. 【趣味学取证】电子数据取证现场勘验知多少?
  8. 正确区分CRM、OA、协同办公平台
  9. Android Mms专题之:联系人管理
  10. 连版《三十六英雄》整理
  11. Failed to read artifact descriptor for xxx:jar的问题解决
  12. python一天学费多少_自学python一天的小项目实战
  13. 5.Abp vNext 地磅无人值守 微信小程序
  14. HTML5 UI 模板
  15. Python:1020 月饼
  16. 领袖的七个非常重要的根性-余世维
  17. 微服务系列:Nacos的搭建演武版(单机+集群+监控)
  18. 数据分析师要掌握SQL到什么程度?
  19. python中横向制表符_python中制表符是什么意思
  20. NR/5G - On demand SI

热门文章

  1. 也谈信息化建设中的“一把手”
  2. 拓扑在计算机领域的应用,复杂网络理论及其在计算机拓扑行为中的应用.doc
  3. jquery跨域获取数据以及分页
  4. oracle java赋予执行_在oracle中执行java例程
  5. Android设备上的蓝牙被搜索到所显示的图标
  6. 锚点定位并距离顶部一定偏移
  7. python自媒体混剪视频_做自媒体混剪视频,万次播放收益150元!混剪视频剪辑技巧分享...
  8. kafka-offset手动提交和自动提交
  9. OpenCV这么简单为啥不学——1.13图片冷白皮(美白)处理
  10. UPC2021个人训练赛第39场 C: 粉兔找妹子(换根dp)