3L 说了前端的方法,我说说后端的方法吧。
新建一个中间件:
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\TrimStrings as Middleware;
class LimitFormRepeatSubmit extends Middleware
{
public function handle($request, Closure $next)
{
/****************************************
* 1. 前端自己生成一个 form_token
* 2. 如果 session 已经存在,则告诉重复提交
****************************************/
$token = $request->input('form_token');
if (cache()->has($token)) {
return back()->with('status', '请不要重复提交');
}
cache([$token => 'value'], 1);
return $next($request);
}
}
前端页面
<form action="">
<input type="hidden" name="form_token" value="{{ str_random(40) }}">
</form>
3L 说了前端的方法,我说说后端的方法吧。
新建一个中间件:
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\TrimStrings as Middleware;
class LimitFormRepeatSubmit extends Middleware
{
public function handle($request, Closure $next)
{
/****************************************
* 1. 前端自己生成一个 form_token
* 2. 如果 session 已经存在,则告诉重复提交
****************************************/
$token = $request->input('form_token');
if (cache()->has($token)) {
return back()->with('status', '请不要重复提交');
}
cache([$token => 'value'], 1);
return $next($request);
}
}
前端页面
<form action="">
<input type="hidden" name="form_token" value="{{ str_random(40) }}">
</form>
@Littlesqx 解释一下吧,免得更多的人问。
- 首先 str_random 总会生成一个唯一的字符串。这就代表我们缓存中的 key 总是唯一的
- 永远永远永远表单的提交只能一个一个提交,如果你能做到两个表单同时提交,也许真的有,我不知道而已?(还有,即使两个表单同时提交也很肯定没有问题的。只要这两个表单都放 form_token 字段,并且使用 str:random 生成两个不同的,前面提到了唯一。一百个也没有问题)
路上下雨,手机码字就暂时写这么多吧!
@DavidNineRoc 这样行不通吧...
前端每次提交都会生成新的 token , 这个新的 token 在你的中间件里面被接收, 所以 if 判断永远都是 false, 做的是无用功啊 :joy:
@weir 你可能需要看一下题目说的是 防止重复提交,而不是多次提交。
重复提交我的定义是:单次的数据提交多次,更多是出现在 AJAX 提交。比如下订单,你点了,网络可能延迟一秒,前端也没有禁用按钮,然后你以为出 bug,然后一直点。这一秒延迟你点了十次,如果你不做验证,就会有十条订单记录。而如果用我说的那个方式,实际上发到后端的都是 form_token,后面的九次都是重复提交的,直接不进入控制器就行了
@DavidNineRoc 这个中间件并不能防止你定义的重复提交. 一秒点十次, 这十次前端提交过来的表单都会携带不同的 token , 也就是说你在中间件里面接收到 token 每次都是不一样的 , if 里面的代码永远都不会起作用.
首先
这个和小程序什么没有任何关系。
你在页面加载的时候生成一个 form_token,只在页面加载时生成,那么其他时候就只会用这一个,那么只要你没有刷新页面,那么就永远不会变,当你提交的带上这个参数提交到后台。 那么你是觉得为什么做不到呢?
1 如果防止表单重复提交我觉得csrf是比较好的方式
2 如果防止数据重复我觉得可以可以在数据库上加上唯一索引 (你觉得要重复的的数据) 然遇到重复的就会抛出异常 这个有点暴力 但是能解决问题 也可以考虑redis存md5
我觉得比较好的做法是通过中间件,请求到来的时候查询redis中是否有根据请求参数、请求路径、ip等变量拼接后加密的md5值(或其他可以唯一识别的值),如果存在,判定为重复请求,如果不存在就往redis中新增,中间件通过后,执行完业务,再通过Terminable中间件将这个值从redis中删除
第一步,用户打开表单,在后端生成一个 token 保存在 session 中,并渲染到表单
$token = rand(10000, 99999);
session('form_token', $token);
<form>
<input type='hidden' name='token' value='{{ $token }}' />
</form>
第二步,表单提交时,匹配 token 有效则执行,执行后清除 session 中的 token
$input_token = request()->input('token');
$token = session('form_token');
if($input_token == $token) {
// todo save
// clear session token
session('form_token', null);
} else {
return '重复提交表单';
}
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class LimitFormRepeatSubmitMiddleware
{
/**
* 限制表单重复提交
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param int $limit 限制时间(缓存时间)
* @return mixed
* @throws \Psr\SimpleCache\InvalidArgumentException
*/
public function handle(Request $request, Closure $next, $limit = 1)
{
// 如果存在表单, 根据 _token 判断
if ($request->filled('_token')) {
$token = $request->input('_token');
$cache_key_form_token = self::class . '::' . $token;
if (cache()->has($cache_key_form_token)) {
if ($request->ajax() || $request->wantsJson() || $request->expectsJson()) {
return response()->json(['success' => false, 'code' => 500, 'message' => "请不要重复提交表单"]);
} else {
return redirect()->back()->with('message', "请不要重复提交表单");
}
}
cache([$cache_key_form_token => $token], $limit);
}
return $next($request);
}
}
推荐文章: