= $todayStart) { $today++; } } return [ 'total' => count($entries), 'ips' => count($ips), 'today' => $today, 'ban' => (int) db()->query('SELECT COUNT(*) FROM ip_blacklist')->fetchColumn(), ]; } /** 访问记录查询(完整 / 精简 + IP、关键词筛选 + 分页) */ function vis_query_access(string $mode, string $ipF, string $q, int $page, int $perPage = 30): array { if (!in_array($mode, ['full', 'compact'], true)) { $mode = 'full'; } $entries = visitors_entries(); $all = []; if ($mode === 'compact') { // 精简模式:按 IP 聚合(最近访问时间 / IP / 访问次数),并默认按访问次数从多到少排序 $grp = []; foreach ($entries as $e) { if ($ipF !== '' && stripos($e['ip'], $ipF) === false) { continue; } if ($q !== '' && stripos($e['url'], $q) === false && stripos($e['method'], $q) === false) { continue; } if (!isset($grp[$e['ip']])) { $grp[$e['ip']] = ['ip' => $e['ip'], 'last_at' => $e['created_at'], 'cnt' => 0]; } $grp[$e['ip']]['cnt']++; } $all = array_values($grp); // 访问次数多的在前;次数相同按最近访问时间倒序;再相同按 IP 升序 usort($all, static function (array $a, array $b): int { if ($a['cnt'] !== $b['cnt']) { return $b['cnt'] <=> $a['cnt']; } $d = strcmp((string) $b['last_at'], (string) $a['last_at']); return $d !== 0 ? $d : strcmp((string) $a['ip'], (string) $b['ip']); }); } else { // 完整模式:逐条明细 foreach ($entries as $e) { if ($ipF !== '' && stripos($e['ip'], $ipF) === false) { continue; } if ($q !== '' && stripos($e['url'], $q) === false && stripos($e['method'], $q) === false) { continue; } $all[] = $e; } } $page = max(1, $page); $total = count($all); $totalPages = max(1, (int) ceil($total / $perPage)); if ($page > $totalPages) { $page = $totalPages; } return [ 'mode' => $mode, 'page' => $page, 'perPage' => $perPage, 'total' => $total, 'totalPages' => $totalPages, 'logs' => array_slice($all, ($page - 1) * $perPage, $perPage), 'query' => ['mode' => $mode, 'ip' => $ipF, 'q' => $q], 'hasFilter' => ($ipF !== '' || $q !== ''), ]; } /** 生成带参数的链接($q 为基础参数,$ov 为覆盖项) */ function vis_make_url(array $q, array $ov = []): string { foreach ($ov as $k => $v) { if ($v === null || $v === '') { unset($q[$k]); } else { $q[$k] = $v; } } return $q ? ('visits.php?' . http_build_query($q)) : 'visits.php'; } /** 分页链接的 AJAX 查询串(供 data-vis-page 使用) */ function vis_page_query(array $q, int $page): string { $q['page'] = $page; return http_build_query($q); } /** 渲染访问记录片段(统计行 + 表格 + 分页 / 空态) */ function vis_render_access(array $d): string { $logs = $d['logs']; $mode = $d['mode']; $q = $d['query']; $page = (int) $d['page']; $totalPages = (int) $d['totalPages']; ob_start(); if ($logs) { ?>
共命中 ; 当前第 / 页,每页 条。
最近访问时间 IP 地址 访问次数
访问时间 IP 地址 请求方式 访问 URL 响应码
= 200 && $code < 300) ? 'good' : (($code >= 300 && $code < 400) ? 'info' : (($code >= 400 && $code < 500) ? 'warn' : 'bad')); ?>
1): ?>
1): ?> ← 上一页 1) { echo ''; } for ($p = $start; $p <= $end; $p++) { if ($p === $page) { echo '' . $p . ''; } else { echo '' . $p . ''; } } if ($end < $totalPages) { echo ''; } ?> 下一页 →
$lf, 'rows' => login_fail_daily_stats($lf, $limit), ]; } /** 登录失败日志片段渲染(表格 / 空态) */ function vis_render_loginfail(array $d): string { $rows = $d['rows']; ob_start(); if ($rows) { ?>
日期 IP 地址 失败次数 最近失败时间 设备信息