prepare('INSERT INTO settings (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value'); $st->execute([$key, $value]); } /* 批量删除黑名单(按 id 列表),同步清除单 IP 规则的风控计数;返回删除条数 */ function bl_delete_ids(PDO $pdo, array $ids): int { $ids = array_values(array_unique(array_filter(array_map('intval', $ids), function ($v) { return $v > 0; }))); if (!$ids) return 0; $ph = implode(',', array_fill(0, count($ids), '?')); $st = $pdo->prepare('SELECT rule FROM ip_blacklist WHERE id IN (' . $ph . ')'); $st->execute($ids); $rules = $st->fetchAll(); $pdo->prepare('DELETE FROM ip_blacklist WHERE id IN (' . $ph . ')')->execute($ids); foreach ($rules as $r) { if (filter_var((string) $r['rule'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { risk_clear_for_ip((string) $r['rule']); } } return count($rules); } /* ---------- POST:黑名单增删 / 清空日志 / 风控规则 ---------- */ $msgKind = ''; $msgText = ''; $banChanged = false; if ($_SERVER['REQUEST_METHOD'] === 'POST') { if (!csrf_verify()) { $msgKind = 'err'; $msgText = '安全校验失败,请刷新页面重试。'; } else { $act = (string) ($_POST['act'] ?? ''); switch ($act) { case 'ban_add': $rule = trim((string) ($_POST['rule'] ?? '')); $note = trim((string) ($_POST['note'] ?? '')); if ($rule === '') { $msgKind = 'err'; $msgText = '请填写要拦截的 IP / 网段。'; break; } $parsed = blacklist_parse_rule($rule); if (!$parsed[0]) { $msgKind = 'err'; $msgText = $parsed[3]; break; } $st = $pdo->prepare('INSERT INTO ip_blacklist (rule, note, created_at, source) VALUES (?,?,?,?)'); $st->execute([$rule, $note, date('Y-m-d H:i:s'), 'manual']); $banChanged = true; $msgKind = 'ok'; $msgText = '黑名单规则已添加,匹配的访问将立即被拒绝。'; break; case 'ban_del': $id = max(0, (int) ($_POST['id'] ?? 0)); if ($id > 0) { $bs = $pdo->prepare('SELECT rule FROM ip_blacklist WHERE id = ? LIMIT 1'); $bs->execute([$id]); $brow = $bs->fetch(); $pdo->prepare('DELETE FROM ip_blacklist WHERE id = ?')->execute([$id]); // 解封单 IP 时同步清除其风控计数,避免“刚解封又被历史计数封回” if ($brow && filter_var((string) $brow['rule'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { risk_clear_for_ip((string) $brow['rule']); } $banChanged = true; $msgKind = 'ok'; $msgText = '黑名单规则已移除。'; } else { $msgKind = 'err'; $msgText = '无效的黑名单记录。'; } break; case 'log_clear': @file_put_contents(ACCESS_LOG_FILE, '', LOCK_EX); $msgKind = 'ok'; $msgText = '访问日志已清空(data/vistors.log)。'; break; case 'risk_save': $clip = function ($v, $d) { $raw = trim((string) ($v ?? '')); $x = ($raw === '') ? (int) $d : (int) $raw; return max(1, min(1000000, $x)); }; settings_set('risk_login_on', isset($_POST['r_login_on']) ? '1' : '0'); settings_set('risk_login_n', (string) $clip($_POST['r_login_n'] ?? null, 10)); settings_set('risk_404_on', isset($_POST['r_404_on']) ? '1' : '0'); settings_set('risk_404_n', (string) $clip($_POST['r_404_n'] ?? null, 100)); settings_set('risk_rate_on', isset($_POST['r_rate_on']) ? '1' : '0'); settings_set('risk_rate_n', (string) $clip($_POST['r_rate_n'] ?? null, 120)); settings_set('risk_tip_main', trim((string) ($_POST['r_tip_main'] ?? ''))); settings_set('risk_tip_sub', trim((string) ($_POST['r_tip_sub'] ?? ''))); $msgKind = 'ok'; $msgText = '风控规则已保存并即时生效。'; break; case 'risk_clear': risk_clear_all(); $msgKind = 'ok'; $msgText = '全部风险计数已清空(黑名单本身不受影响)。'; break; case 'loginlog_clear': login_log_clear(); $msgKind = 'ok'; $msgText = '登录日志已清空(含成功与失败记录)。'; break; case 'ban_batch': $n = bl_delete_ids($pdo, (array) ($_POST['bid'] ?? [])); $banChanged = true; $msgKind = $n > 0 ? 'ok' : 'err'; $msgText = $n > 0 ? '已批量移除 ' . $n . ' 条黑名单规则(对应单 IP 计数已清除)。' : '未勾选任何需要移除的规则。'; break; case 'ban_clear_auto': $rows = $pdo->query("SELECT rule FROM ip_blacklist WHERE source = 'auto'")->fetchAll(); $cnt = count($rows); $pdo->exec("DELETE FROM ip_blacklist WHERE source = 'auto'"); foreach ($rows as $r) { if (filter_var((string) $r['rule'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { risk_clear_for_ip((string) $r['rule']); } } blacklist_rules(true); $banChanged = true; $msgKind = 'ok'; $msgText = '已一键移除全部风控自动封禁(共 ' . $cnt . ' 条),相关 IP 风控计数已同步清除。'; break; case 'ban_clear_all': $pdo->exec('DELETE FROM ip_blacklist'); risk_clear_all(); blacklist_rules(true); $banChanged = true; $msgKind = 'ok'; $msgText = '已清空全部黑名单,风险计数一并清零。'; break; default: $msgKind = 'err'; $msgText = '未知操作。'; } } } /* ---------- 概要统计(数据来源:data/vistors.log) ---------- */ $visStat = vis_stat_all(); $statTotal = $visStat['total']; $statIps = $visStat['ips']; $statToday = $visStat['today']; $statBan = $visStat['ban']; /* ---------- 筛选参数(供表单回显)与访问记录查询 ---------- */ $mode = (string) ($_GET['mode'] ?? 'full'); if (!in_array($mode, ['full', 'compact'], true)) { $mode = 'full'; } $ipF = substr(trim((string) ($_GET['ip'] ?? '')), 0, 64); $q = substr(trim((string) ($_GET['q'] ?? '')), 0, 100); $page = max(1, (int) ($_GET['page'] ?? 1)); $perPage = 30; $visAccess = vis_query_access($mode, $ipF, $q, $page, $perPage); /* ---------- 当前访问者信息(用于黑名单自查提示) ---------- */ $curIp = client_ip(); if ($curIp === '') { $curIp = '未知'; } $curHit = null; if ($curIp !== '未知') { if ($banChanged) { blacklist_rules(true); } $curHit = blacklist_ip_hit($curIp); } /* ---------- 自动风控规则配置与当前 IP 计数 ---------- */ $rcfg = risk_rule_cfg(); $curRisk = ($curIp !== '未知') ? risk_row($curIp) : null; /* ---------- 登录信息:当前会话 / 历史成功登录 / 登录失败按「IP × 日期」聚合 ---------- */ $lgStat = login_log_stat(); $lgRecent = login_log_recent_ok(15); $lgFailF = substr(trim((string) ($_GET['lf'] ?? '')), 0, 64); $lgFailData = vis_query_loginfail($lgFailF, 200); $curAdmin = (string) ($_SESSION['admin'] ?? ''); $curLoginAt = (string) ($_SESSION['login_at'] ?? ''); $curLoginIp = (string) ($_SESSION['login_ip'] ?? ($curIp !== '未知' ? $curIp : '')); $curLoginUa = (string) ($_SESSION['login_ua'] ?? ''); $curLoginDevice = (string) ($_SESSION['login_device'] ?? ''); if ($curLoginDevice === '') { $curLoginDevice = login_ua_brief((string) ($_SERVER['HTTP_USER_AGENT'] ?? '')); } /* ---------- 黑名单规则(直接读表展示,便于核对备注与来源) ---------- */ $banRows = $pdo->query('SELECT id, rule, note, created_at, source FROM ip_blacklist ORDER BY id DESC')->fetchAll(); $banAutoCount = 0; foreach ($banRows as $b) { if (((string) ($b['source'] ?? 'manual')) === 'auto') $banAutoCount++; } layout_head('风控管理'); admin_topbar('visits'); ?>

风控管理

记录访客对本站的动态请求(含后台),完整模式逐条展示访问明细,精简模式按 IP 汇总次数;黑名单 / 自动风控对所有访问统一生效,命中即返回 403 风控提示页,后台与登录页不例外。

访问概况
累计访问
独立 IP
今日访问
黑名单规则
日志按行追加写入 data/vistors.log(制表符分隔:时间 / IP / 请求方式 / 响应码 / URL),完整保留不设上限;可自行归档或删除该文件。
登录信息
当前用户
本次登录时间
本次登录 IP
本次登录设备

历史登录记录(最近 条成功登录;今日成功 次)

登录时间 登录 IP 登录设备(浏览器) 用户名
暂无成功登录记录(本次登录为首次记录)。
登录失败日志
今日失败次数
失败涉及 IP
今日成功登录
登录日志总数
重置 按「IP × 日期」聚合:同一 IP 当天的失败次数与最近一次设备信息
记录存于数据库 login_log 表(含成功与失败、时间 / 用户名 / IP / 设备)。
访问记录
重置
风控规则
失败次数 ≥ 次 → 永久封禁(建议 5-20)
累计次数 ≥ 次 → 永久封禁(建议 50-300)
60 秒内 ≥ 次 → 永久封禁(多人共用出口 IP 时建议关闭)

计数方式:404 与登录失败只要发生即累计(规则关闭也照常计数,便于后台观察);规则启用且达到阈值时自动封禁,保存后即时生效。自动封禁在黑名单中标注“风控自动”,可随时移除解封(解封时同步清除该 IP 计数)。

主提示(留空=默认)
次行说明(留空=默认)
当前访问 IP()计数:登录失败 次 · 404 · 本分钟请求
黑名单管理

支持三种写法:单个 IP、CIDR 网段(如 203.0.113.0/24)、星号通配段(如 203.0.113.*192.168.*.*); 目前仅按 IPv4 匹配。黑名单即时生效且不受「站长提示」开关影响,命中 IP 的前台与后台(含登录页)访问一律被拒绝。如需解除,请从未被封禁的网络进入后台移除该规则,或直接在 data/homepage.db 的 ip_blacklist 表中删除记录。

当前访问 IP: —— 命中规则「」(),全站(前台与后台)访问将被拦截 当前访问 IP: —— 未命中任何黑名单规则
0): ?>
风控自动 手动 (无备注)' ?>

尚未配置黑名单规则。启用上方「自动风控」后,异常 IP 会自动记录到这里;也可在下方手动添加规则。