= $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 / 访问次数),首次出现即该 IP 最新一条
$grp = [];
$order = [];
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];
$order[] = $e['ip'];
}
$grp[$e['ip']]['cnt']++;
}
foreach ($order as $ip) {
$all[] = $grp[$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) {
?>
共命中 = (int) $d['total'] ?> = $mode === 'compact' ? '个 IP' : '条记录' ?>;
当前第 = $page ?> / = $totalPages ?> 页,每页 = (int) $d['perPage'] ?> 条。
1): ?>
= $d['hasFilter'] ? '该筛选条件下暂无访问记录,可点击「重置」查看全部。' : '还没有访问记录,访问站点前台页面后将在这里实时展示。' ?>
$lf,
'rows' => login_fail_daily_stats($lf, $limit),
];
}
/** 登录失败日志片段渲染(表格 / 空态) */
function vis_render_loginfail(array $d): string
{
$rows = $d['rows'];
ob_start();
if ($rows) {
?>
= $d['filter'] !== '' ? '该 IP 暂无登录失败记录。' : '暂无登录失败记录。' ?>