springMVC如何防止表單重復(fù)提交詳解
?前言
在系統(tǒng)中,有些接口如果重復(fù)提交,可能會(huì)造成臟數(shù)據(jù)或者其他的嚴(yán)重的問題,所以我們一般會(huì)對與數(shù)據(jù)庫有交互的接口進(jìn)行重復(fù)處理
- 首先可以在前端做一層控制。當(dāng)前端觸發(fā)操作時(shí),或彈出確認(rèn)界面,或 disable 禁用按鈕等等,但是這并不能徹底解決問題。假設(shè)我們不是從客戶端提交,而是被其他的系統(tǒng)調(diào)用,還會(huì)遇到這種問題
- 為了徹底解決問題,還需要在后端對接口做防重處理
一般會(huì)引起表單重復(fù)提交的場景
- 在網(wǎng)絡(luò)延遲的情況下讓用戶有時(shí)間點(diǎn)擊多次 submit 按鈕導(dǎo)致表單重復(fù)提交
- 表單提交后用戶點(diǎn)擊【刷新】按鈕導(dǎo)致表單重復(fù)提交
- 用戶提交表單后,點(diǎn)擊瀏覽器的【后退】按鈕回退到表單頁面后進(jìn)行再次提交
防止表單重復(fù)提交
單機(jī)
實(shí)現(xiàn)的思路步驟
通過當(dāng)前用戶上一次請求的 url 和參數(shù),驗(yàn)證是否是重復(fù)的請求
- 攔截器攔截請求,將上一次請求的 url 和參數(shù)和這次的對比
- 判斷,是否一致說明重復(fù)提交并記錄日志
代碼實(shí)現(xiàn)
創(chuàng)建自定義注解
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface SameUrlData { }
創(chuàng)建自定義攔截器
public class SameUrlDataInterceptor extends HandlerInterceptorAdapter { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (handler instanceof HandlerMethod) { HandlerMethod handlerMethod = (HandlerMethod) handler; Method method = handlerMethod.getMethod(); // 是否有 SameUrlData 注解 SameUrlData annotation = method.getAnnotation(SameUrlData.class); if (annotation != null) { // 如果重復(fù)相同數(shù)據(jù) if (repeatDataValidator(request)) { response.sendRedirect("/error/409"); return false; } else { return true; } } return true; } else { return super.preHandle(request, response, handler); } } /** * 驗(yàn)證同一個(gè)url數(shù)據(jù)是否相同提交,相同返回true */ private boolean repeatDataValidator(HttpServletRequest httpServletRequest) { String params = JsonMapper.toJsonString(httpServletRequest.getParameterMap()); String url = httpServletRequest.getRequestURI(); Map<String, String> map = new HashMap<>(); map.put(url, params); String nowUrlParams = map.toString(); Object preUrlParams = httpServletRequest.getSession().getAttribute("repeatData"); // 如果上一個(gè)數(shù)據(jù)為null,表示沒有重復(fù)提交 if (preUrlParams == null) { httpServletRequest.getSession().setAttribute("repeatData", nowUrlParams); return false; // 否則,已經(jīng)有過提交了 } else { // 如果上次url+數(shù)據(jù) 和 本次url+數(shù)據(jù)相同,則表示重復(fù)添加數(shù)據(jù) if (preUrlParams.toString().equals(nowUrlParams)) { return true; // 如果上次 url+數(shù)據(jù) 和 本次url加數(shù)據(jù)不同,則不是重復(fù)提交 } else { httpServletRequest.getSession().setAttribute("repeatData", nowUrlParams); return false; } } } }
將自定義攔截器添加到容器
<mvc:interceptor> <mvc:mapping path="/**"/> <bean class="com.chinagdn.base.common.interceptor.SameUrlDataInterceptor"/> </mvc:interceptor>
controller 層
@Controller public class TestController { // 在 controller 層使用 @SameUrlData 注解即可 @SameUrlData @RequestMapping(value = "save", method = RequestMethod.POST) public String save(@Valid LoginUser loginUser, Errors errors, RedirectAttributes redirectAttributes, Model model) throws Exception { //..... } }
分布式
很顯然在分布式的情況下,使用上述方法已無法防止表單重復(fù)提交了;一者在分布式部署下 session 是不共享的,二者使用 上一次請求的 url 和參數(shù)和這次的對比 已無法對比請求的 url 和參數(shù)了。在此情況下,就可以使用 redisson 的分布式鎖來實(shí)現(xiàn)了
實(shí)現(xiàn)的思路步驟
- 使用分布式鎖 redisson 來嘗試獲取一把鎖
- 如果成功獲取鎖,再獲取判斷 redis 中的 key 值是否存在(key 值自定義)
- 如果 key 值不存在,則證明不是重復(fù)請求,再給 redis 中存入數(shù)據(jù)(使用 UUID 不重復(fù));反之,則證明是重復(fù)請求進(jìn)行提交
- 最后,釋放分布式鎖
代碼實(shí)現(xiàn)
Maven 依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.redisson</groupId> <artifactId>redisson-spring-boot-starter</artifactId> <version>3.11.0</version> </dependency>
配置文件配置 redis 信息
# redis 的配置 spring.redis.host=127.0.0.1 spring.redis.port=6379 spring.redis.password= spring.redis.database=0 spring.redis.timeout=2000 spring.redis.lettuce.pool.max-active=10 spring.redis.lettuce.pool.max-wait=2 spring.redis.lettuce.pool.min-idle=5 spring.redis.lettuce.pool.max-idle=10
RedissonClient 配置類
@Configuration public class RedissonConfig { @Bean public RedissonClient redissonClient() { Config config = new Config(); config.useSingleServer().setAddress("redis://127.0.0.1:6379"); RedissonClient client = Redisson.create(config); return client; } }
controller 層,偽代碼如下
@Controller public class TestController { @Autowired private StringRedisTemplate stringRedisTemplate; @Autowired private RedissonClient redissonClient; @PostMapping(path="/registerLock") @ResponseBody public ResultMap registerLock(UserDto userDto) { RLock rLock = redissonClient.getLock(userDto.getUserName()); // redis中的key值 String key = userDto.getUserName() + "-redissonLock"; boolean resultLock = rLock.tryLock(30, 10, TimeUnit.SECONDS); if (resultLock) { try { // 如果不存在key if (!stringRedisTemplate.hasKey(key)) { // 給redis中存入數(shù)據(jù) stringRedisTemplate.opsForValue().set(key,UUID.randomUUID().toString(),10L,TimeUnit.SECONDS); // .....................你的業(yè)務(wù) } // 如果存在,則提示不可重復(fù)提交 return new ResultMap().fail().message("請勿重復(fù)提交"); } catch (Exception e) { return new ResultMap().fail().message("獲取redisson分布式鎖異常"); } } finally { // 釋放鎖 rLock.unlock(); } return null; } }
分布式鎖防止表單重復(fù)提交參考:http://www.dbjr.com.cn/article/230608.htm
總結(jié)
到此這篇關(guān)于springMVC如何防止表單重復(fù)提交的文章就介紹到這了,更多相關(guān)springMVC防止表單重復(fù)提交內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringMVC?bean加載控制的實(shí)現(xiàn)分析
SpringMVC是一種基于Java,實(shí)現(xiàn)了Web?MVC設(shè)計(jì)模式,請求驅(qū)動(dòng)類型的輕量級(jí)Web框架,即使用了MVC架構(gòu)模式的思想,將Web層進(jìn)行職責(zé)解耦?;谡埱篁?qū)動(dòng)指的就是使用請求-響應(yīng)模型,框架的目的就是幫助我們簡化開發(fā),SpringMVC也是要簡化我們?nèi)粘eb開發(fā)2023-02-02我總結(jié)的幾種@Transactional失效原因說明
這篇文章主要是我總結(jié)的幾種@Transactional失效原因說明,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11WMTS中TileMatrix與ScaleDenominator淺析
這篇文章主要為大家介紹了WMTS中TileMatrix與ScaleDenominator淺析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03SpringBoot使用Mybatis-Generator配置過程詳解
這篇文章主要介紹了SpringBoot使用Mybatis-Generator配置過程詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-02-02spring cloud hystrix 超時(shí)時(shí)間使用方式詳解
這篇文章主要介紹了spring cloud hystrix 超時(shí)時(shí)間使用方式,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01json如何解析混合數(shù)組對象到實(shí)體類的list集合里去
這篇文章主要介紹了json解析混合數(shù)組對象到實(shí)體類的list集合里去的操作,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06Java?數(shù)據(jù)結(jié)構(gòu)深入理解ArrayList與順序表
ArrayList?類是一個(gè)可以動(dòng)態(tài)修改的數(shù)組,與普通數(shù)組的區(qū)別就是它是沒有固定大小的限制,我們可以添加或刪除元素。ArrayList?繼承了?AbstractList?,并實(shí)現(xiàn)了?List?接口,順序表是將元素順序地存放在一塊連續(xù)的存儲(chǔ)區(qū)里,元素間的順序關(guān)系由它們的存儲(chǔ)順序自然表示2022-04-04