ToB企服应用市场:ToB评测及商务社交产业平台
标题:
java 表单避免重复提交?
[打印本页]
作者:
我爱普洱茶
时间:
2024-8-5 07:47
标题:
java 表单避免重复提交?
面试经历
记得刚结业的时间,有一次去参加面试。
上来面试官问我:“你们项目中是怎么做防重复提交的?”
一开始听到这个题目是蒙圈的,支支吾吾半天没答复出来。
然背面试官直接来一道算法题,喜闻乐见解面试失败。
多年过去,固然很少接触到控台应用,但是近期对于防止重复提交却有了一点本身的心得。
在这里分享给大家,盼望你工作大概面试中遇到雷同的题目时,对你有所帮助。
本文将从以下几个方面展开:
(1)重复提交产生的原因
(2)什么是幂等性
(3)针对重复提交,前后端的办理方案
(4)如果实现一个防重复提交工具
产生原因
由于重复点击大概网络重发
eg:
点击提交按钮两次;
点击革新按钮;
使用浏览器后退按钮重复之前的操作,导致重复提交表单;
使用浏览器汗青纪录重复提交表单;
浏览器重复的HTTP请求;
nginx重发等情况;
分布式RPC的try重发等;
主要有 2 个部门:
(1)前端用户操作
(2)网络请求可能存在重试
当然也不排除一些用户的恶意操作。
java 表单避免重复提交
题目
就是同一份信息,重复的提交给服务器。
场景
点击F5革新页面: 当用户点击submit将已经写好的表单数据提交到服务器时,可以在浏览器的url看到地址和参数的变化,但因为网速等题目,用户当前页面并未革新,大概点击革新页面,造成表单重复提交。
重复点击提交按钮: 因为网络题目,未能及时跳转显示内容,部门用户可能会出于心急重复提交提交按钮,造成多次提交内容到服务器。
前进后退操作 :有些用户在进行某些工作操作时,可能出于必要大概某种情况,进行后退操作,浏览刚才填入的信息,在进行后退和前进的操作可能也会造成表单数据重复提交到服务器。
使用浏览器汗青纪录重复访问: 某些用户可能会出于好奇,使用浏览器的汗青纪录功能重复访问提交页面,同样会造成表单重复提交题目。
办理思路
前端
方案一:禁用按钮提交
设置标志位,提交之后克制按钮。像一些短信验证码的按钮一般都会加一个前端的按钮禁用,毕竟发短信是必要钞票滴~
ps: 以前写前端就用过这种方式。
长处
简单。基本可以防止重复点击提交按钮造成的重复提交题目。
缺陷
前进后退操作,大概F5革新页面等题目并不能得到办理。
最告急的一点,前端的代码只能防止不懂js的用户,如果碰到懂得js的编程人员,那js方法就没用了。
方案二:设置HTTP报头
设置HTTP报头,控制表单缓存,使得所控制的表单不缓存信息,这样用户就无法通过重复点击按钮去重复提交表单。
[/code]但是这样做也有范围性,用户在提交页面点击革新也会造成表单的重复提交。
[size=4]方案三:通过 PRG 设计模式[/size]
用来防止F5革新重复提交表单。
PRG模式通过相应页面Header返回HTTP状态码进行页面跳转替代相应页面跳转过程。
具体过程如下:
[indent]客户端用POST方法请求服务器端数据变更,服务器对客户端发来的请求进行处理重定向到另一个结果页面上,客户端全部对页面的显示请求都用get方法告知服务器端,这样做,后退再前进或革新的举动都发出的是get请求,不会对server产生任何数据更改的影响。
[/indent]这种方法实现起来相对比较简单,但此方法也不能防止全部情况。例如用户多次点击提交按钮;恶意用户避开客户端预防多次提交本领,进行重复提交请求;
下面谈一谈后端的防止重复提交。
[size=5]后端[/size]
[size=4]幂等性[/size]
如果是注册或存入数据库的操作,可以通过在数据库中字段设立唯一标识来办理,这样在进行数据库插入操作时,因为每次插入的数据都相同,数据库会拒绝写入。
这样也避免了向数据库中写入垃圾数据的情况,同时也办理了表单重复提交题目。
但是这种方法在业务逻辑上感觉是说不外去的,本来该有的逻辑,却因为数据库该有的设计隐藏了。
而且这种方法也有一定的功能范围性,只适用于某系特定的插入操作。
[list]
[*]实现方式
[/list]这种操作,都必要有一个唯一标识。[b]数据库中做唯一索引束缚,重复插入直接报错。[/b]
[list]
[*]缺点
[/list]有很大的束缚性。
一般都是最后的一道防线,当请求走到数据库层的时间,一般已经斲丧了较多的资源。
[size=4]session 方法[/size]
Java 使用Token令牌防止表单重复提交的步骤:
[list=1]
[*]在服务器端生成一个唯一的随机标识号,专业术语称为Token(令牌),同时在当前用户的Session域中保存这个Token。
[*]将Token发送到客户端的Form表单中,在Form表单中使用隐藏域来存储这个Token,表单提交的时间连同这个Token一起提交到服务器端。
[*]在服务器端判断客户端提交上来的Token与服务器端生成的Token是否一致,如果不一致,那就是重复提交了,此时服务器端就可以不处理重复提交的表单。如果相同则处理表单提交,处理完后打扫当前用户的Session域中存储的标识号。
[/list]下面的场景将拒绝处理用户提交的表单请求
[list=1]
[*]存储Session域中的Token(令牌)与表单提交的Token(令牌)差别。
[*]当前用户的Session中不存在Token(令牌)。
[/list]这里的 session 按照单机和分布式,可以使用 redis/mysql 等办理分布式的题目。
这种方法算是比较经典的办理方案,但是必要前后端的共同。
下面来介绍通过加锁的方式,实现纯后台修改的实现。
[size=3]为什么要设置一个隐藏域?[/size]
这个题目我一开始没想明白,我认为,进入页面的时间设置一个session而且再token设值,添加的时间把这个值删掉。然后这样我们再按F5的时间就没办法重复提交了(因为这个时间判断session为空)。我觉得这样就ok了,设置hidden域感觉没任何须要。
然而简直是图样图森破,对于一般用户这样当然是可以的。
但是对于恶意用户呢?假如恶意用户开两个浏览器窗口(同一浏览器的窗口公用一个session)这样窗口1提交完,系统删掉session,窗口1停留着,他打开第二个窗口进入这个页面,系统又为他们添加了一个session,这个时间窗口1按下F5,那么直接重复提交!
以是,我们必须得用hidden隐藏一个uuid的token,而且在后台比较它是否与session中的值一致,只有这样才能保证F5是不可能被重复提交的!
[size=4]直接加锁[/size]
为了避免短时间的重复提交,直接使用加锁的方式。
长处:不必要前端共同修改,纯后端。
缺点:无法像 token 方法,准确的限定为单次。只能限制固定时间内的操作一次。
个人理解:前端的方式依然是防君子不防小人。直接通过限制固定时间内无法操作,来限制重复提交。
这个时间不能太长,也不能太短,一般建议为 10S 左右,根据本身的真实业务调整。
锁也是同样的道理,token 实在也可以理解为一种特殊的锁。
锁同样可以分为单机锁+分布式的锁。
[size=5]个人理解[/size]
前后端结合,前端减轻后端的压力,同时提升用户体验。
后端做最后的把关,避免恶意用户操作,确保数据的数据的精确性。
[size=6]如何设计防重复提交框架[/size]
[size=5]整体思路[/size]
session 方式和加锁的方式,二者现实上是可以统一的。
此处做一个抽象:
(1)获取锁
(2)释放锁
[size=5]session 流程 + 前端[/size]
session 的获取 token 让用户本身处理,比如打开页面,放在隐藏域。现实上这是一个释放锁的过程。
当操作的时间,只有 token 信息和后台一致,才认为是获取到了锁。用完这个锁就不绝被锁住了,必要重新获取 token,才能释放锁。
全部的 session 都应该有 token 的失效时间,避免累计一堆无用的脏数据。
[size=5]纯后端[/size]
[list]
[*]获取锁
[/list]当请求的时间,直接根据 user_id(大概其他标识)+请求信息(自界说)=唯一的 key
然后把这个 key 存储在 cache 中。
如果是本地 map,可以本身实现 key 的清空。
大概借助 guava 的 key 逾期,redis 的主动逾期,乃至数据库的逾期都可以。
原理是雷同的,就是限制一定时间内,无法重复操作。
[list]
[*]释放锁
[/list]固定时间后,key 被清空后就释放了锁。
[size=5]注解界说[/size]
只有一个针对锁的获取:
[list]
[*]acquire
[*]tryAcquire
[/list]传入信息。
至于锁的释放,则交给实现者本身实现。
[size=4]属性[/size]
[list=1]
[*]锁的获取策略 = 内存 RAM
[/list]内存 ConcurrentHashMP
Guava
Encache
redis
mysql
...
可以基于 session,大概基于 锁,
此处实现基于锁。
[list=1]
[*]锁的逾期时间 = 5min
[/list]无论基于什么方式,这个值都必要。
只不外基于 session 的交给实现者处理,此处只是为了统一属性。
[size=6]基于字节码的实现[/size]
[size=5]测试案例[/size]
[size=4]maven 引入[/size]
[code]<dependency>
<group>com.github.houbb</group>
<artifact>resubmit-core</artifact>
<version>0.0.3</version>
</dependency>
复制代码
服务类编写
指定 5s 内克制重复提交。
@Resubmit(ttl = 5)
public void queryInfo(final String id) {
System.out.println("query info: " + id);
}
复制代码
测试代码
相同的参数 5s 内直接提交2次,就会报错。
@Test(expected = ResubmitException.class)
public void errorTest() {
UserService service = ResubmitProxy.getProxy(new UserService());
service.queryInfo("1");
service.queryInfo("1");
}
复制代码
核心实现
界说注解
Resubmit.java
首先,我们界说一个注解。
[code]import com.github.houbb.resubmit.api.support.ICache;import com.github.houbb.resubmit.api.support.IKeyGenerator;import com.github.houbb.resubmit.api.support.ITokenGenerator;import java.lang.annotation.*;/** * @author binbin.hou * @since 0.0.1 */@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)@Documentedpublic @interface Resubmit { /** * 缓存实现策略 * @return 实现 * @since 0.0.1 */ Class
欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/)
Powered by Discuz! X3.4