当前位置:   article > 正文

laravel中防止表单重复提交的综合解决方案_laravel防止表单重复提交

laravel防止表单重复提交

【本文转载自Hyeshttp://www.hao124.net/article/14

怎样防止表单重复提交,通过搜索引擎能搜到很多结果,但很零散,系统性不强,正好前几天做了这个功能,决定记录下来。
根据数据流向的过程,分别在三个“点”控制表单的重复提交,如下:

第一,用户触发submit时,前端js控制提交按钮的状态,用户触发提交即设置按钮的disabled属性为true,防止重复点击;
第二,在数据到达服务器并通过验证时,服务端根据维护的一个状态以控制表单重复提交,通常是利用session;
第三,数据入库时,数据库添加unique索引,当然这个得根据需要来选择;

下面详细说各部分的内容:

1、前端js控制按钮状态

监听表单的submit事件,在其中将提交按钮设置为不可用,代码如下:

$('form').submit(function() {
        $('button[type=submit]').attr('disabled', true);
    });
  • 1
  • 2
  • 3

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

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

2、服务端通过维护一个状态来控制表单重复提交

网络上常用做法解析

网上搜了一下,说得最多的做法是:

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

这种做法有两个弊端

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

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

b、不够简洁,要知道这里解决的问题是要防止表单重复提交,完全没有必要生成一个动态的类似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');
}
  • 1
  • 2
  • 3
  • 4
  • 5

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,'请忽重复注册');
    }

    //省略下面的验证、注册逻辑等代码
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

【补充】如果是参数验证失败,比如手机号已注册之类的,你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为key
            return $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];
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100

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

3、数据库控制,添加unique索引

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

总结

防止表单重复提交,功能虽然简单,但要保证’万无一失’,还是得费不少脑细胞,这是作为一个程序员该有严谨作风。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/449297
推荐阅读
  

闽ICP备14008679号