package com.test.bijihoudaun.util; import lombok.extern.slf4j.Slf4j; import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; import java.util.LinkedHashMap; import java.util.Map; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * 登录锁定工具类 * 使用本地内存存储登录失败记录,带容量限制和定期清理 */ @Slf4j public class LoginLockUtil { // 最大失败次数 private static final int MAX_FAILED_ATTEMPTS = 5; // 锁定时间(分钟) private static final int LOCK_TIME_MINUTES = 30; // 失败记录过期时间(分钟) private static final int RECORD_EXPIRE_MINUTES = 60; // 最大存储记录数(防止内存溢出) private static final int MAX_RECORDS = 10000; // 登录失败记录:key=用户名,value=失败记录 private static final LRUCache attempts = new LRUCache<>(MAX_RECORDS); private static final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); // 定期清理线程池 private static final ScheduledExecutorService cleanupScheduler = Executors.newSingleThreadScheduledExecutor(r -> { Thread t = new Thread(r, "login-lock-cleanup"); t.setDaemon(true); return t; }); static { // 每10分钟清理一次过期记录 cleanupScheduler.scheduleAtFixedRate(LoginLockUtil::cleanupExpiredLocks, 10, 10, TimeUnit.MINUTES); } private static class LoginAttempt { int failedCount; LocalDateTime lastAttemptTime; LocalDateTime lockUntil; LoginAttempt() { this.failedCount = 0; this.lastAttemptTime = LocalDateTime.now(); this.lockUntil = null; } boolean isExpired() { return ChronoUnit.MINUTES.between(lastAttemptTime, LocalDateTime.now()) > RECORD_EXPIRE_MINUTES; } } /** * 简单的 LRU 缓存实现 */ private static class LRUCache extends LinkedHashMap { private final int maxSize; LRUCache(int maxSize) { super(maxSize, 0.75f, true); this.maxSize = maxSize; } @Override protected boolean removeEldestEntry(Map.Entry eldest) { boolean shouldRemove = size() > maxSize; if (shouldRemove) { log.warn("登录锁定记录达到上限,移除最旧的记录: {}", eldest.getKey()); } return shouldRemove; } } /** * 记录登录失败 * @param username 用户名 */ public static void recordFailedAttempt(String username) { if (username == null || username.isEmpty()) return; lock.writeLock().lock(); try { LoginAttempt attempt = attempts.computeIfAbsent(username, k -> new LoginAttempt()); attempt.failedCount++; attempt.lastAttemptTime = LocalDateTime.now(); // 达到最大失败次数,锁定账号 if (attempt.failedCount >= MAX_FAILED_ATTEMPTS) { attempt.lockUntil = LocalDateTime.now().plusMinutes(LOCK_TIME_MINUTES); log.warn("账号 [{}] 已被锁定 {} 分钟", username, LOCK_TIME_MINUTES); } } finally { lock.writeLock().unlock(); } } /** * 记录登录成功(清除失败记录) * @param username 用户名 */ public static void recordSuccess(String username) { if (username == null || username.isEmpty()) return; lock.writeLock().lock(); try { attempts.remove(username); } finally { lock.writeLock().unlock(); } } /** * 检查账号是否被锁定 * @param username 用户名 * @return true-已锁定,false-未锁定 */ public static boolean isLocked(String username) { if (username == null || username.isEmpty()) return false; lock.readLock().lock(); try { LoginAttempt attempt = attempts.get(username); if (attempt == null) return false; // 检查是否仍在锁定时间内 if (attempt.lockUntil != null) { return LocalDateTime.now().isBefore(attempt.lockUntil); } return false; } finally { lock.readLock().unlock(); } } /** * 获取剩余锁定时间(秒) * @param username 用户名 * @return 剩余秒数,如果未锁定返回0 */ public static long getRemainingLockTime(String username) { if (username == null || username.isEmpty()) return 0; lock.readLock().lock(); try { LoginAttempt attempt = attempts.get(username); if (attempt == null || attempt.lockUntil == null) return 0; long remaining = ChronoUnit.SECONDS.between(LocalDateTime.now(), attempt.lockUntil); return Math.max(0, remaining); } finally { lock.readLock().unlock(); } } /** * 获取剩余失败次数 * @param username 用户名 * @return 剩余次数 */ public static int getRemainingAttempts(String username) { if (username == null || username.isEmpty()) return MAX_FAILED_ATTEMPTS; lock.readLock().lock(); try { LoginAttempt attempt = attempts.get(username); if (attempt == null) return MAX_FAILED_ATTEMPTS; return Math.max(0, MAX_FAILED_ATTEMPTS - attempt.failedCount); } finally { lock.readLock().unlock(); } } /** * 清理过期的锁定记录 */ private static void cleanupExpiredLocks() { lock.writeLock().lock(); try { LocalDateTime now = LocalDateTime.now(); attempts.entrySet().removeIf(entry -> { LoginAttempt attempt = entry.getValue(); // 未锁定且长时间没有登录的记录 if (attempt.lockUntil == null) { return attempt.isExpired(); } // 锁定已过期的记录 return now.isAfter(attempt.lockUntil); }); } finally { lock.writeLock().unlock(); } } /** * 获取当前记录数量(用于监控) */ public static int getRecordCount() { lock.readLock().lock(); try { return attempts.size(); } finally { lock.readLock().unlock(); } } }