package Sdf1_login; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; public class RiskControlManager { private final Main plugin; // 玩家当前是否处于风控状态 private final Map riskActive = new ConcurrentHashMap<>(); // 密码验证连续错误次数 private final Map passwordAttempts = new ConcurrentHashMap<>(); // 邮箱验证码连续错误次数 private final Map emailAttempts = new ConcurrentHashMap<>(); // 风控触发时间(5分钟过期) private final Map riskTriggerTime = new ConcurrentHashMap<>(); // 临时封禁到期时间 private final Map tempBanExpiry = new ConcurrentHashMap<>(); private static final long RISK_TIMEOUT = 5 * 60 * 1000L; // 5分钟 private static final int MAX_ATTEMPTS = 3; private static final long TEMP_BAN_DURATION = 10 * 60 * 1000L; // 10分钟 public RiskControlManager(Main plugin) { this.plugin = plugin; } /** * 检查玩家是否被临时封禁 */ public boolean isTempBanned(String name) { Long expiry = tempBanExpiry.get(name); if (expiry == null) return false; if (System.currentTimeMillis() >= expiry) { tempBanExpiry.remove(name); return false; } return true; } /** * 获取临时封禁剩余秒数 */ public int getTempBanRemaining(String name) { Long expiry = tempBanExpiry.get(name); if (expiry == null) return 0; return (int) ((expiry - System.currentTimeMillis()) / 1000); } /** * 检查玩家是否需要风控验证 * 条件:IP变化 + 有背包数据(老玩家) */ public boolean shouldTrigger(String name, String currentIP) { if (isTempBanned(name)) return false; if (riskActive.getOrDefault(name, false)) return false; // 获取上次记录的IP String lastIP = (String) plugin.getDb().getField(name, "last_ip"); if (lastIP == null || lastIP.isEmpty()) return false; // 新玩家不触发 // IP相同不触发 if (lastIP.equals(currentIP)) return false; // 有背包数据的老玩家 + IP变化 → 触发风控 String invData = (String) plugin.getDb().getField(name, "inventory_data"); if (invData != null && !invData.isEmpty()) { return true; } return false; } /** * 触发风控:冻结玩家,保存并隐藏背包 */ public void trigger(Player p, String currentIP) { String name = p.getName(); riskActive.put(name, true); riskTriggerTime.put(name, System.currentTimeMillis()); passwordAttempts.put(name, 0); emailAttempts.put(name, 0); // 保存背包并冻结 plugin.hideInventory(p); p.sendMessage("§c§l[风控系统] §f检测到异常登录!"); p.sendMessage("§e当前IP: §f" + currentIP); p.sendMessage("§e请选择验证方式:"); p.sendMessage("§a1. §f密码验证 §7(输入: 密码 <你的密码>)"); p.sendMessage("§a2. §f邮箱验证 §7(输入: 邮箱)"); p.sendMessage("§7风控有效期: 5分钟"); } /** * 处理密码验证 */ public boolean handlePassword(Player p, String input) { String name = p.getName(); if (!riskActive.getOrDefault(name, false)) return false; // 检查过期 if (isExpired(name)) { expireRisk(name); return false; } int attempts = passwordAttempts.getOrDefault(name, 0) + 1; passwordAttempts.put(name, attempts); String salt = (String) plugin.getDb().getField(name, "password_salt"); if (salt == null) return false; String hash = PasswordUtils.hash(input, salt); if (plugin.getDb().checkPassword(name, hash)) { passRisk(p); return true; } if (attempts >= MAX_ATTEMPTS) { failRisk(p); } else { p.sendMessage("§c§l[风控系统] §f密码错误!剩余 " + (MAX_ATTEMPTS - attempts) + " 次机会"); } return true; } /** * 发送邮箱验证码 */ public void sendEmailCode(Player p) { String name = p.getName(); if (!riskActive.getOrDefault(name, false)) return; if (isExpired(name)) { expireRisk(name); return; } String emailAddr = (String) plugin.getDb().getField(name, "email"); if (emailAddr == null || emailAddr.isEmpty()) { p.sendMessage("§c§l[风控系统] §f你未绑定邮箱,无法使用邮箱验证"); return; } if (!plugin.getEmail().isConfigured()) { p.sendMessage("§c§l[风控系统] §f邮件服务未配置,请使用密码验证"); return; } String code = PasswordUtils.generateVerifyCode(); plugin.getChatInput().setVerifyCode(p, code, "risk_control"); boolean sent = plugin.getEmail().sendVerifyCode(emailAddr, name, code); if (sent) { p.sendMessage("§a§l[风控系统] §f验证码已发送到 " + emailAddr); p.sendMessage("§e请输入: 验证 <验证码>"); } else { p.sendMessage("§c§l[风控系统] §f验证码发送失败,请使用密码验证"); } } /** * 处理邮箱验证码 */ public boolean handleEmailCode(Player p, String code) { String name = p.getName(); if (!riskActive.getOrDefault(name, false)) return false; if (isExpired(name)) { expireRisk(name); return false; } int attempts = emailAttempts.getOrDefault(name, 0) + 1; emailAttempts.put(name, attempts(p, code, "risk_control")) { passRisk(p); return true; } if (attempts >= MAX_ATTEMPTS) { failRisk(p); } else { p.sendMessage("§c§l[风控系统] §f验证码); if (plugin.getChatInput().checkVerifyCode错误!剩余 " + (MAX_ATTEMPTS - attempts) + " 次机会"); } return true; } /** * 验证通过:解冻,记录新IP */ private void passRisk(Player p) { String name = p.getName(); riskActive.remove(name); passwordAttempts.remove(name); emailAttempts.remove(name); riskTriggerTime.remove(name); // 记录新IP String ip = plugin.getPlayerIP(p); if (ip != null) plugin.getDb().setField(name, "last_ip", ip); plugin.recordIPLogin(p); // 还原背包 plugin.restoreInventory(p); plugin.getLoggedIn().add(name); plugin.getDb().setLoggedIn(name, true); p.sendMessage("§a§l[风控系统] §f验证通过!已解除风控限制"); p.playSound(p.getLocation(), org.bukkit.Sound.BLOCK_NOTE_BLOCK_CHIME, 1.0f, 0.79f); } /** * 验证失败3次:临时封禁 + 邮件报警 */ private void failRisk(Player p) { String name = p.getName(); riskActive.remove(name); passwordAttempts.remove(name); emailAttempts.remove(name); riskTriggerTime.remove(name); // 临时封禁10分钟 tempBanExpiry.put(name, System.currentTimeMillis() + TEMP_BAN_DURATION); p.sendMessage("§c§l[风控系统] §f验证失败次数过多,临时封禁10分钟"); // 邮件报警 String emailAddr = (String) plugin.getDb().getField(name, "email"); if (emailAddr != null && !emailAddr.isEmpty() && plugin.getEmail().isConfigured()) { String ip = plugin.getPlayerIP(p); plugin.getEmail().sendVerifyCode(emailAddr, name, "[Sdf1_login] 风控报警: 玩家 " + name + " IP(" + ip + ")验证失败3次,已临时封禁10分钟"); } // 踢出 p.kickPlayer("§c§l[风控系统] §f验证失败,临时封禁10分钟,请稍后再试"); } /** * 检查风控是否过期 */ private boolean isExpired(String name) { Long trigger = riskTriggerTime.get(name); if (trigger == null) return true; return System.currentTimeMillis() - trigger > RISK_TIMEOUT; } /** * 风控过期处理 */ private void expireRisk(String name) { riskActive.remove(name); passwordAttempts.remove(name); emailAttempts.remove(name); riskTriggerTime.remove(name); } /** * 玩家是否处于风控状态 */ public boolean isRiskActive(String name) { return riskActive.getOrDefault(name, false); } /** * 定时清理过期数据 */ public void cleanup() { long now = System.currentTimeMillis(); riskTriggerTime.entrySet().removeIf(e -> { if (now - e.getValue() > RISK_TIMEOUT) { riskActive.remove(e.getKey()); passwordAttempts.remove(e.getKey()); emailAttempts.remove(e.getKey()); return true; } return false; }); tempBanExpiry.entrySet().removeIf(e -> now >= e.getValue()); } }