精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

因為一個重復提交,被面試官瘋狂diss

開發 項目管理
平時開發項目的時候,你是否遇到這樣的困惑,用戶不停的點擊按鈕向后端提交數據,而你卻束手無策!

 平時開發項目的時候,你是否遇到這樣的困惑,用戶不停的點擊按鈕向后端提交數據,而你卻束手無策!

[[330832]]

一、故事

記得以前面試的時候,面試官拋出來這么一個問題,就是后端如何防止重復提交訂單?

當時的我剛工作一年多,工作經歷也不是很豐富,腦子里第一個想到的就是,這個前端就可以解決吧,然后面試官說必須要在后臺處理這個問題,之后這場面試也就涼了。

面試結束之后,就開始百度查詢資料,除了廣告占頭條比較吸引人以外,也沒找到啥可行的答案,然后請教各路大佬之后,終算是有了一個比較可靠的解決方案。(后文會詳細分享)

前些天在群里也看到有個朋友在討論這個問題,這讓我也想起了之前的那段經歷,今天小編就和大家一起來討論一下如何防止重復提交這個問題!

二、問題場景

重復提交,從名字上看,顧名思義,就是多次提交數據,例如支付的時候,假如同一筆訂單多次支付,就會造成多次扣款,其后果可想而知!

像這樣的案例比比皆是,如果將場景進行歸納,我們會發現主要有兩類:

  • 第一類:由于用戶誤操作或者網絡卡頓,可能會造成多次點擊表單提交按鈕或者刷新提交頁面,就會造成重復提交;
  • 第二類:黑客或惡意用戶使用postman、jmeter等工具重復惡意提交表單,攻擊網站,從而造成重復提交;

這兩類嚴重的時候,甚至會直接造成系統宕機!

三、解決方案

說了這么多,那如何防止重復提交數據呢?

毫無疑問,肯定是從前端、后端同時入手!

3.1、前端解決方法

通過 JavaScript 來屏蔽提交按鈕,當用戶點擊提交按鈕后,屏幕彈出遮罩層提示數據加載中....!

直到后端返回結果或者前端請求超時時,再將其遮罩層關閉,從而實現防止表單重復提交!

3.2、后端解決方法

雖然前端通過屏蔽操作按鈕,防止用戶重復提交數據,但是如果黑客直接繞過前端給后端提交數據時,那么后端肯定也必須要做防止重復提交的驗證。

方案一:給數據庫增加唯一鍵約束(不推薦)

起初,最開始想到的就是,在控制層給數據做驗證,例如用戶注冊,當用戶手機號或者郵箱已經存在,則直接提示提交失敗。

  1. @RequestMapping(value = "/register"
  2. public boolean register(@RequestBody UserDto userDto) throws Exception { 
  3.     //檢查郵件是否已經注冊 
  4.     QueryWrapper<User> queryWrapper = new QueryWrapper(); 
  5.     queryWrapper.eq("user_email",userDto.getUserEmail()); 
  6.     User dbUser = userService.getOne(queryWrapper); 
  7.     if(dbUser ! = null){ 
  8.         throw new CommonExecption("當前郵箱已被注冊,請使用新的郵箱注冊或者通過密碼找回操作!"); 
  9.     } 
  10.     return userService.insert(userDto); 

如果想更加安全一點,可以在數據庫中給關鍵字段增加唯一鍵約束,如果用戶郵箱已經插入到數據庫,會直接拋異常,提示當前郵箱已經注冊!

  1. try { 
  2.     userService.insert(userDto); 
  3. } catch (Exception e) { 
  4.     log.error("用戶插入失敗",e); 
  5.     throw new CommonExecption("當前郵箱已被注冊,請使用新的郵箱注冊!"); 

這種方案在某些場景下是有效果的,例如請求不是非常頻繁,可以采用這種方式。

那如果請求非常頻繁,而且服務層需要處理的邏輯非常多的時候,這種方案就會遇到很大的瓶頸。

以訂單支付為例,當用戶支付時,首先會對訂單數據做各種基礎驗證,接著走風控系統,鑒別是否是機器人操作,風控系統通過之后,再對接銀行系統查詢用戶金額是否充足,如果充足就申請扣款,扣款成功之后,更新訂單狀態,同時將訂單的數據推送給中心倉庫,等待發貨。

當然這個只是一個基礎的流程,實際的處理邏輯比這個要復雜的多,此時我們也不能像上面介紹的那樣對某個關鍵字做唯一約束,同時整個處理邏輯所需的時間也相對比較長,假如有幾個請求同時過來,其結果可想而知!

方案二:利用緩存ID防止重復提交(推薦)

設想一下,前端在請求后端的時候,先從后端緩存中獲取一個唯一的ID,在請求提交數據的時候帶上這個唯一的ID,后端檢查緩存中是否存在這個ID,如果存在,就進行業務處理,處理完畢之后,從緩存中將這個ID移除掉,如果在處理過程中,前端又再次提交,此時緩存中的ID狀態還沒有被移除,直接提示:數據處理中,不要重復提交....,具體流程如下!

  • 先編寫一個緩存工具類
  1. /** 
  2.  * 緩存工具類 
  3.  */ 
  4. public class CacheUtil { 
  5.  
  6.     //hashMap線程安全類 
  7.     private static Map<String,Object> cacheMap = new ConcurrentHashMap<>(); 
  8.  
  9.     /** 
  10.      * 添加緩存 
  11.      * @param key 
  12.      * @param value 
  13.      */ 
  14.     public static void addCache(String key,Object value){ 
  15.         cacheMap.put(key, value); 
  16.     } 
  17.  
  18.     /** 
  19.      * 設置緩存 
  20.      * @param key 
  21.      * @param value 
  22.      */ 
  23.     public static void setValue(String key,Object value){ 
  24.         cacheMap.put(key, value); 
  25.     } 
  26.  
  27.     /** 
  28.      * 獲取緩存 
  29.      * @param key 
  30.      * @return 
  31.      */ 
  32.     public static Object getValue(String key){ 
  33.         return cacheMap.get(key); 
  34.     } 
  35.  
  36.     /** 
  37.      * 判斷key是存在 
  38.      * @param key 
  39.      * @return 
  40.      */ 
  41.     public static boolean containKey(String key){ 
  42.         return cacheMap.containsKey(key); 
  43.     } 
  44.  
  45.     /** 
  46.      * 移除緩存 
  47.      * @param key 
  48.      */ 
  49.     public static void removeCache(String key){ 
  50.         cacheMap.remove(key); 
  51.     } 
  52.  
  • 再編寫一個獲取唯一ID的方法
  1. @PostMapping("/getSubmitToken"
  2. public Object getSubmitToken(){ 
  3.     String submitToken = UUID.randomUUID().toString(); 
  4.     //將事務請求唯一ID放入緩存池 
  5.     CacheUtil.addCache(submitToken, "false"); 
  6.     //將ID返回給前端 
  7.     JSONObject result = new JSONObject(); 
  8.     result.put("submitToken", submitToken); 
  9.     return result; 
  • 接著編寫一個注解,用于需要驗證重復提交的方法上
  1. @Target({ElementType.METHOD, ElementType.TYPE}) 
  2. @Retention(RetentionPolicy.RUNTIME) 
  3. public @interface SubmitToken { 
  4.  
  5.     boolean value() default true
  • 然后編寫一個攔截器,用于類或者方法上有@SubmitToken注解的驗證處理
  1. /** 
  2.  * 重復提交攔截器 
  3.  */ 
  4. public class SubmitTokenInterceptor implements HandlerInterceptor { 
  5.  
  6.     @Override 
  7.     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 
  8.         //如果不是映射到方法,直接通過 
  9.         if(!(handler instanceof HandlerMethod)){ 
  10.             return true
  11.         } 
  12.         //如果類或者方法有SubmitToken注解,則進行重復提交驗證 
  13.         HandlerMethod handlerMethod = (HandlerMethod) handler; 
  14.         if (handlerMethod.getBeanType().isAnnotationPresent(SubmitToken.class) || handlerMethod.getMethod().isAnnotationPresent(SubmitToken.class)) { 
  15.             final String submitToken = request.getParameter("submitToken"); 
  16.             if(StringUtils.isEmpty(submitToken)){ 
  17.                 throw new CommonException("submitToken不能為空!"); 
  18.             } 
  19.             if(!CacheUtil.containKey(submitToken)){ 
  20.                 throw new CommonException("submitToken失效,請重新獲取!"); 
  21.             } 
  22.             Object value = CacheUtil.getValue(submitToken); 
  23.             if(!"false".equals(value)){ 
  24.                 throw new CommonException("數據正在處理,請不要重復提交"); 
  25.             } 
  26.             //驗證通過之后,將submitToken對應的值設置為正在處理 
  27.             CacheUtil.setValue(submitToken, "true"); 
  28.         } 
  29.         return true
  30.     } 
  31.  
  32.  
  33.     @Override 
  34.     public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { 
  35.         //業務處理完畢之后,將submitToken從緩存中移除 
  36.         final String submitToken = request.getParameter("submitToken"); 
  37.         if(StringUtils.isNotEmpty(submitToken)){ 
  38.             CacheUtil.removeCache(submitToken); 
  39.         } 
  40.     } 
  • 最后將@SubmitToken注解用于需要進行重復提交的方法或者類上
  1. /** 
  2.  * 將SubmitToken用于增、刪、改的方法或者類上 
  3.  */ 
  4. @SubmitToken 
  5. @RequestMapping(value = "/register"
  6. public boolean register(@RequestBody UserDto userDto) throws Exception { 
  7.     //...... 

在開發的時候,我們只需將@SubmitToken用于增、刪、改的方法上即可,當前端在提交數據的時候,先通過/getSubmitToken接口獲取一個submitToken也就是唯一ID,然后再提交請求的時候,帶上這個參數即可!

當你真正在使用的時候,對于緩存類你會發現還有很大的優化空間,本例采用的是ConcurrentHashMap作為緩存類,隨著提交請求量越來越多,緩存類所占用的空間也越來越大,最后很有可能會OOM。

因此有兩種解決辦法:

  • 第一種:編寫一個緩存實體類,里面存放有效期,然后弄一個線程來掃描緩存map,到達過期的數據就將其移除。
  • 第二種:將需要緩存的數據寫入到redis,同時設置過期時間。

如果是小項目,第一種方法就基本可以解決,如果是中大型項目,那么推薦使用 redis 搭建高可用的緩存集群,同時一定要注意 key 的設計,最好采用單獨的前綴,例如submittoken-uuid-+ 項目名稱作為前綴,方便后期擴展的時候緩存數據遷移!

四、總結

本文主要圍繞后端如何防止重復提交數據問題進行一些總結,可能也有遺漏的地方,歡迎網友點評、吐槽!

 

責任編輯:武曉燕 來源: Java極客技術
相關推薦

2021-12-17 07:30:42

排序算法效率

2020-12-29 06:51:32

線程源碼SQL

2020-08-03 07:04:54

測試面試官應用程序

2021-07-05 22:09:53

面試官CollectionsJDK7

2023-01-18 10:35:49

MySQL數據庫

2024-04-17 08:18:22

MyBatis批量插入SQL

2020-05-12 11:05:54

MySQL索引數據庫

2021-02-28 07:43:28

請求提交方案

2022-04-08 08:26:03

JavaHTTP請求

2021-09-28 13:42:55

Chrome Devwebsocket網絡協議

2025-10-20 04:00:00

2025-11-11 09:25:19

2023-12-25 09:03:33

MySQL索引數據庫

2021-05-19 08:17:35

秒殺場景高并發

2020-05-13 14:35:47

HashMap面試官Java

2023-07-31 08:26:09

2022-01-10 11:04:41

單鏈表面試編程

2022-08-18 20:02:04

JSLRU緩存

2021-03-17 08:39:24

作用域作用域鏈JavaScript

2021-03-16 22:25:06

作用域鏈作用域JavaScript
點贊
收藏

51CTO技術棧公眾號

久久精品中文| 成人深夜福利| 成人免费高清在线| 日本亚洲欧洲色| 四虎地址8848| 大桥未久女教师av一区二区| 欧美午夜无遮挡| 亚洲三区在线| 欧美一级淫片免费视频魅影视频| 欧美专区在线| 久久久国产成人精品| 精品人妻伦一二三区久| 四虎成人在线| 亚洲图片一区二区| 一区二区日本伦理| 五月天婷婷激情网| 精品一区二区三区在线观看| 91精品国产成人www| 国产精品久久久久久久久久久久冷| 亚洲精品久久久久久一区二区| 国产午夜精品久久久| 精品日韩久久久| 大桥未久在线播放| 国产精品热久久久久夜色精品三区| 在线观看免费成人| 亚洲精品一区二区三区av| 亚洲精品国产一区二| 男人的j进女人的j一区| 91av在线国产| 久久久久香蕉视频| 91成人影院| 伊人久久久久久久久久久| 粉嫩av懂色av蜜臀av分享| 精品一区二区三区中文字幕 | 一级黄色录像免费看| 日韩精品系列| 成人h动漫精品一区二| 91免费版网站入口| 在线观看免费高清视频| 免费亚洲网站| 97久久伊人激情网| 免费人成在线观看| 一区二区电影在线观看| 日韩在线观看免费全集电视剧网站| 中文字幕一区二区人妻在线不卡| 都市激情亚洲| 亚洲福利小视频| 年下总裁被打光屁股sp | 人妻一区二区三区四区| 精品亚洲aⅴ乱码一区二区三区| 5252色成人免费视频| 麻豆影视在线播放| 国产二区精品| 最新国产成人av网站网址麻豆| 精品国产午夜福利在线观看| 亚洲三级电影| 欧美午夜电影在线播放| 欧美日韩在线免费播放| av毛片午夜不卡高**水| 夜夜爽夜夜爽精品视频| 在线观看三级网站| 欧美jizz18性欧美| 国产精品污www在线观看| 欧美自拍资源在线| 九色在线观看视频| 久久蜜桃av一区二区天堂| 久久久久资源| 黄色三级网站在线观看| 97精品久久久午夜一区二区三区| 国产欧美日韩一区二区三区| 国产三级第一页| 国产福利一区二区| 成人片在线免费看| 全部免费毛片在线播放一个| www.亚洲精品| 狠狠色综合网站久久久久久久| 日本久久一级片| 99久久99精品久久久久久| 久久av一区二区三区亚洲| 黄色成人一级片| 国产亚洲精品免费| 亚洲综合视频一区| 成人免费视屏| 亚洲一区二区视频在线观看| 欧美男女爱爱视频| 芒果视频成人app| 欧美中文一区二区三区| 毛片毛片毛片毛| 久久久精品区| 欧美www视频| 小毛片在线观看| 日韩在线黄色| 美女福利精品视频| 国产小视频在线观看免费| 亚洲美女少妇无套啪啪呻吟| 日本aⅴ大伊香蕉精品视频| 亚洲国产成人无码av在线| 丝瓜av网站精品一区二区| 国产精品美女免费看| 国产欧美久久久精品免费| 成人一区在线看| 欧美色图亚洲自拍| 日本在线免费播放| 亚洲国产综合91精品麻豆| 国产午夜大地久久| 日韩成人亚洲| 日韩女优电影在线观看| 亚洲第一香蕉网| 98精品久久久久久久| 欧美黑人一区二区三区| 亚洲av无码精品一区二区| 寂寞少妇一区二区三区| 国产一区二区免费电影| 91在线导航| 亚洲va国产天堂va久久en| 国产又黄又大又粗视频| 国产成人免费视频网站视频社区| 亚洲丁香婷深爱综合| 日韩不卡av在线| 亚洲黄色精品| 国产日韩一区在线| 天天操天天插天天射| 国产精品美女久久久久久久久| 800av在线免费观看| 在线国产成人影院| 亚洲精品在线一区二区| 国产视频三区四区| 亚洲乱码视频| 亚洲自拍偷拍视频| 午夜不卡视频| 色天天综合久久久久综合片| 麻豆av免费看| 国产韩国精品一区二区三区| 欧美在线性爱视频| 亚洲黄色精品视频| 中文字幕一区在线观看| 无码无遮挡又大又爽又黄的视频| 哺乳挤奶一区二区三区免费看| 色综合伊人色综合网| 东京热一区二区三区四区| 国产主播一区二区三区| 欧美日韩精品免费观看| 丁香高清在线观看完整电影视频| 欧美日韩不卡在线| 亚洲理论片在线观看| 99精品视频免费| 国产成人成网站在线播放青青| 欧美极品视频| 欧美日韩一级二级三级| av免费播放网站| 久久黄色影院| 久久伊人一区二区| 欧美成人xxx| 欧美日韩国产激情| 伦理片一区二区| 欧美激情麻豆| 18成人免费观看网站下载| 欧美三级理伦电影| 欧美色偷偷大香| 九九热免费在线| 日本va欧美va欧美va精品| 日本在线观看不卡| 日韩成人动漫| 亚洲香蕉av在线一区二区三区| 依依成人综合网| 26uuu国产在线精品一区二区| 黄页免费在线观看视频| 日本久久成人网| 欧美一区亚洲一区| 男女污视频在线观看| 色偷偷久久人人79超碰人人澡| 中文乱码人妻一区二区三区视频| 欧美一级做a爰片免费视频| 精品一区二区三区影院在线午夜| 亚洲欧洲日产国码无码久久99| 国产探花视频在线观看| 欧美老年两性高潮| 国产亚洲精品精品精品| 久久蜜桃资源一区二区老牛| 欧美性天天影院| 美女网站视频一区| 日韩在线国产精品| 91麻豆成人精品国产| 樱花草国产18久久久久| 国产chinesehd精品露脸| 亚洲调教视频在线观看| 国产一区二区黄色| 欧美xoxoxo| 色播久久人人爽人人爽人人片视av| 在线观看免费视频一区| 亚洲人成网站在线| 亚洲av成人精品一区二区三区| 国产精品社区| 亚洲欧洲中文| 大桥未久女教师av一区二区| 欧美性受xxx| 2021av在线| 欧美精品一区二| 无码人妻av一区二区三区波多野 | 韩国三级视频在线观看| 亚洲日本国产| 亚洲欧洲精品一区| 成人在线超碰| 国产精品入口福利| 国产www视频在线观看| 亚洲欧美国内爽妇网| 国产一级片一区二区| 亚洲欧美aⅴ...| 久久无码人妻精品一区二区三区 | 国产精品激情自拍| av在线看片| 亚洲欧美国产精品专区久久 | 日韩欧美专区在线| 探花视频在线观看| 国产女同互慰高潮91漫画| 中文字幕一区久久| 国产农村妇女毛片精品久久莱园子 | 欧美另类videos| 伊人久久大香线蕉综合网站| 亚洲free嫩bbb| 欧美成a人片在线观看久| 欧美俄罗斯乱妇| 蜜桃免费在线| 日韩av中文字幕在线| 国产裸体无遮挡| 日本高清不卡aⅴ免费网站| 久久国产一级片| 国产精品免费视频观看| 精品少妇人妻一区二区黑料社区 | 欧美三级情趣内衣| 国产精品日韩一区二区| 亚洲三级电影| 午夜精品久久久久久久99热浪潮| h片在线免费观看| 伊人伊成久久人综合网小说| 亚洲欧美色视频| 欧美一区二区三区爱爱| 中文字幕+乱码+中文乱码91| 精品美女国产在线| 久久无码精品丰满人妻| 最新日韩在线视频| 国产精品久久免费观看| 2022国产精品视频| 四虎成人在线播放| 国内精品在线播放| 蜜臀av免费观看| 日韩影院在线观看| 妞干网在线免费视频| 妖精视频成人观看www| 日韩黄色片在线| 欧美伊人影院| 国产又黄又爽免费视频| 日韩专区精品| 亚洲精品乱码视频| 亚洲五月综合| 日本精品免费视频| 午夜影院欧美| 三年中国中文在线观看免费播放| 久久国产精品成人免费观看的软件| 久久精品日产第一区二区三区精品版| 91亚洲精品视频在线观看| 999精品视频一区二区三区| 国产午夜久久av| 亚洲一区二区三区久久| 久久久久久爱| 成人一区二区三区四区| 国产suv精品一区| 国产一区二区三区四区五区加勒比 | 精品高清久久| 亚洲欧洲日本国产| 亚洲成人av| 91免费国产精品| 一区视频在线| 日本久久久精品视频| 肉丝袜脚交视频一区二区| av无码精品一区二区三区| 日韩一区精品字幕| 性生活免费在线观看| 精品一区二区三区蜜桃| 精品综合久久久久| 福利电影一区二区| 日韩片在线观看| 国产日韩亚洲欧美综合| 国产精品久久免费观看| 亚洲欧美另类在线| 国产无码精品在线播放| 日韩欧美主播在线| 中文字幕一区二区人妻| 欧美一区二区三区免费在线看| 国产高清精品软件丝瓜软件| 亚洲人成电影在线| 理论片午午伦夜理片在线播放| 久久国产精品影视| 草草在线视频| 国产精品欧美一区二区| 国产日韩在线观看视频| 精品一区在线播放| 成人高清av| 免费cad大片在线观看| 久久久久国产精品一区三寸 | 国产成人免费在线观看| 在线观看国产网站| 中文字幕在线不卡一区 | 色综合久久网| 成年人看的毛片| 日本不卡视频一二三区| 欧美熟妇精品一区二区| 久久久91精品国产一区二区精品| 日本美女黄色一级片| 午夜精品成人在线| 在线观看黄色网| 亚洲欧美精品一区| 日韩影视在线| 国产精品美女久久久久av超清| 一区二区网站| 日韩免费电影一区二区三区| 国户精品久久久久久久久久久不卡| 精品www久久久久奶水| 国产福利电影一区二区三区| av黄色在线免费观看| 亚洲午夜一二三区视频| 中文字幕在线观看视频一区| 日韩精品在线一区二区| 加勒比一区二区三区在线| 欧美黄色片在线观看| 黄色精品视频网站| 久久综合伊人77777麻豆| 最新精品国产| 亚洲精品20p| 国产区在线观看成人精品| 国产精品成人久久| 欧美一级搡bbbb搡bbbb| 国产一级二级三级在线观看| 69视频在线播放| 亚洲高清在线一区| 9999在线观看| 免费亚洲电影在线| 中国美女乱淫免费看视频| 亚洲成人黄色影院| www.色播.com| 精品国产拍在线观看| 国产成人精品123区免费视频| 久久久久久亚洲精品不卡4k岛国| 激情综合亚洲| 中文字幕一二三| 中文字幕一区二区三区在线播放 | 91国产丝袜在线播放| 亚州男人的天堂| 欧美激情国产精品| 精品视频在线播放一区二区三区 | 在线精品视频在线观看高清| 9l视频白拍9色9l视频| 国产亚洲自拍一区| 成人免费视频毛片| 亚洲国产精品专区久久| 另类视频在线| 国产嫩草一区二区三区在线观看| 911久久香蕉国产线看观看| 天天干天天草天天| 国产精品超碰97尤物18| 一级片在线观看视频| 色偷偷av亚洲男人的天堂| 性欧美video另类hd尤物| 夜夜爽www精品| 极品少妇xxxx精品少妇偷拍| 四虎精品免费视频| 日韩精品中文字幕在线不卡尤物| 羞羞网站在线看| 国产精品毛片va一区二区三区| 亚洲欧洲一区二区天堂久久| 在线黄色免费网站| 精品国产31久久久久久| 欧美xxx.com| 国产精品丝袜视频| 欧美成人激情| 91精品国产三级| 亚洲一区二区黄色| 性感美女一级片| 国产精品第8页| 五月开心六月丁香综合色啪| 天天干天天草天天| 亚洲一卡二卡三卡四卡| 天天综合天天综合| 欧美一级在线亚洲天堂| 精品国产网站| 欧美特黄aaa| 亚洲国产日产av| 牛牛热在线视频| 成人免费淫片视频软件| 午夜精品久久| 苍井空张开腿实干12次| 日韩欧美亚洲范冰冰与中字| 成人好色电影| 国产精品传媒毛片三区| 久久狠狠婷婷| 丝袜美腿小色网| 精品视频久久久久久| 欧美美女福利视频| www国产无套内射com| 久久精品人人做|