解析在下面
附贈代碼
private static class CodeInfo {String code;long timestamp;CodeInfo(String code, long timestamp) {this.code = code;this.timestamp = timestamp;}}
// 存儲驗證碼(郵箱 -> 驗證碼信息)(保證線程安全) 以免中途更改郵箱private static final ConcurrentHashMap<String, CodeInfo> codeMap = new ConcurrentHashMap<>();
String email = request.getEmail();// 檢查郵箱是否發送過驗證碼if (!codeMap.containsKey(email)) {response = "EMAIL_CHANGED"; // 郵箱被更改或未發送驗證碼break;}
分步解析:如何完善注冊邏輯以檢測郵箱中途更換
1. 核心邏輯設計
? 目標:確保用戶注冊時使用的郵箱必須與發送驗證碼的郵箱一致,防止中途篡改。
? 關鍵機制:
? 使用 ConcurrentHashMap<String, CodeInfo>
存儲 郵箱 → 驗證碼信息。
? 注冊時檢查提交的郵箱是否已發送過驗證碼(存在于 codeMap
中)。
? 若郵箱未發送過驗證碼或已被更換,返回明確錯誤碼 EMAIL_CHANGED
。
2. 關鍵代碼解析
2.1 驗證碼存儲結構
private static final ConcurrentHashMap<String, CodeInfo> codeMap = new ConcurrentHashMap<>();
? 鍵(Key):用戶郵箱(唯一標識)。
? 值(Value):CodeInfo
對象,包含驗證碼和生成時間戳。
? 線程安全:ConcurrentHashMap
確保多線程環境下的安全訪問。
2.2 發送驗證碼邏輯(Case 4)
case 4:String email = request.getEmail();String code = handleSendRegisterCode(request); // 生成并發送驗證碼codeMap.put(email, new CodeInfo(code, System.currentTimeMillis())); // 存儲郵箱與驗證碼response = "驗證碼發送成功";break;
? 行為:用戶請求發送驗證碼時,將郵箱和驗證碼存入 codeMap
。
? 防篡改:后續注冊必須使用同一郵箱提交驗證碼。
2.3 注冊邏輯(Case 2)
case 2:String email = request.getEmail();String inputCode = request.getVerification();// 檢查郵箱是否發送過驗證碼if (!codeMap.containsKey(email)) {response = "EMAIL_CHANGED"; // 郵箱被中途更換或未發送驗證碼break;}// 驗證驗證碼是否正確或過期if (!validateCode(email, inputCode)) {response = "驗證碼錯誤或已過期";break;}// 執行注冊...break;
? 關鍵檢查:codeMap.containsKey(email)
確保注冊郵箱必須與發送驗證碼的郵箱一致。
? 錯誤處理:若郵箱不一致,返回 EMAIL_CHANGED
。
3. 驗證碼驗證方法
private boolean validateCode(String email, String inputCode) {CodeInfo storedInfo = codeMap.get(email);if (storedInfo == null) return false; // 郵箱未發送過驗證碼boolean expired = (System.currentTimeMillis() - storedInfo.timestamp) > 300_000; // 5分鐘過期boolean valid = storedInfo.code.equalsIgnoreCase(inputCode); // 不區分大小寫if (valid) codeMap.remove(email); // 驗證成功移除記錄return !expired && valid;
}
? 行為:
? 檢查驗證碼是否過期(5分鐘)。
? 檢查驗證碼是否匹配。
? 驗證成功后移除記錄,防止重復使用。
4. 防止中途更換郵箱的流程
- 用戶操作:
? 輸入郵箱 A → 發送驗證碼 → 后端存儲A → CodeInfo
。 ? 修改郵箱為 B → 直接提交注冊(未重新發送驗證碼)。 - 后端檢測:
? 注冊時檢查codeMap.containsKey(B)
→ 不存在。 ? 返回錯誤碼EMAIL_CHANGED
。 - 前端提示:
else if ("EMAIL_CHANGED".equals(response)) {showErrorAlert("檢測到郵箱已更改,請重新獲取驗證碼");
}
5. 測試場景驗證
場景 | 后端行為 | 前端提示 |
---|---|---|
用戶未發送驗證碼直接注冊 | codeMap 無記錄 → 返回 EMAIL_CHANGED | “檢測到郵箱已更改,請重新獲取驗證碼” |
用戶發送驗證碼后更改郵箱未重發 | 新郵箱不在 codeMap → 返回 EMAIL_CHANGED | “檢測到郵箱已更改,請重新獲取驗證碼” |
用戶發送驗證碼后未更改郵箱 | 郵箱存在于 codeMap → 繼續驗證碼檢查 → 注冊成功 | “注冊成功” |
6. 潛在問題與優化
- 問題:用戶可能通過并發請求繞過驗證碼發送限制。
優化:在發送驗證碼時增加頻率限制(如1分鐘內同一郵箱只能發送一次)。 - 問題:
codeMap
在分布式環境下無法共享。
優化:改用 Redis 存儲驗證碼,支持分布式和自動過期。 - 問題:前端未正確傳遞郵箱參數。
調試:添加日志輸出,確認后端收到的郵箱與發送驗證碼時一致。
7. 最終總結
? 完善邏輯:通過 codeMap
的鍵綁定和注冊時的郵箱檢查,有效防止中途更換郵箱。
? 錯誤處理:明確返回 EMAIL_CHANGED
,提升用戶體驗。
? 擴展性:當前方案適用于單機環境,分布式場景需升級存儲方案。