IP 名单管理

与 dashboard 使用相同登录密码;若已在后台登录可刷新本页。

PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ]); } catch (PDOException $e) { http_response_code(500); echo '错误

数据库连接失败。

'; exit; } /** 校验分组属于当前 type,返回 true 或 false */ function groupBelongsToType(PDO $pdo, int $groupId, string $type): bool { $st = $pdo->prepare('SELECT 1 FROM ip_groups WHERE id = ? AND type = ? LIMIT 1'); $st->execute([$groupId, $type]); return (bool) $st->fetchColumn(); } // —— POST:新建分组 —— if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'create_group') { $name = trim((string) ($_POST['group_name'] ?? '')); $postType = ($_POST['list_type'] ?? '') === 'white' ? 'white' : 'black'; if ($name === '') { $flash = '分组名称不能为空'; $flashOk = false; } else { $st = $pdo->prepare('INSERT INTO ip_groups (name, type) VALUES (?, ?)'); $st->execute([$name, $postType]); $flash = '分组已创建'; header('Location: ' . $self . '?type=' . urlencode($postType) . '&msg=' . urlencode($flash) . '&ok=1'); exit; } } // —— POST:批量导入 IP —— if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'bulk_import') { $groupId = (int) ($_POST['import_group_id'] ?? 0); $raw = (string) ($_POST['ip_lines'] ?? ''); if ($groupId <= 0 || !groupBelongsToType($pdo, $groupId, $type)) { $flash = '请选择有效的分组'; $flashOk = false; } else { $lines = preg_split('/\R/u', $raw) ?: []; $uniqueFromText = []; foreach ($lines as $line) { $ip = trim($line); if ($ip === '') { continue; } $uniqueFromText[$ip] = true; } $candidateIps = array_keys($uniqueFromText); $exSt = $pdo->prepare('SELECT ip_address FROM ip_list WHERE group_id = ?'); $exSt->execute([$groupId]); $already = []; while ($row = $exSt->fetch(PDO::FETCH_ASSOC)) { $already[$row['ip_address']] = true; } $toInsert = []; foreach ($candidateIps as $ip) { if (!isset($already[$ip])) { $toInsert[] = $ip; } } $inserted = 0; $stmt = $pdo->prepare('INSERT IGNORE INTO ip_list (group_id, ip_address) VALUES (?, ?)'); $pdo->beginTransaction(); try { foreach ($toInsert as $ip) { $stmt->execute([$groupId, $ip]); $inserted += $stmt->rowCount(); } $pdo->commit(); } catch (Throwable $e) { $pdo->rollBack(); $flash = '导入失败:' . $e->getMessage(); $flashOk = false; } if ($flashOk) { $skippedDupText = count($candidateIps) - count($toInsert); $flash = '批量导入完成:新插入 ' . $inserted . ' 条;文本去重后 ' . count($candidateIps) . ' 条;本组已存在跳过 ' . $skippedDupText . ' 条'; header('Location: ' . $self . '?type=' . urlencode($type) . '&msg=' . urlencode($flash) . '&ok=1'); exit; } } } // —— POST:按行批量删除 IP(当前 type 下;可选限定分组)—— if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'bulk_delete_ips') { $scopeGroup = (int) ($_POST['bulk_delete_group_id'] ?? 0); $raw = (string) ($_POST['bulk_delete_lines'] ?? ''); if ($scopeGroup > 0 && !groupBelongsToType($pdo, $scopeGroup, $type)) { $flash = '批量删除:分组无效'; $flashOk = false; } else { $lines = preg_split('/\R/u', $raw) ?: []; $ips = []; foreach ($lines as $line) { $ip = trim($line); if ($ip !== '') { $ips[$ip] = true; } } $ips = array_keys($ips); if ($ips === []) { $flash = '批量删除:未填写任何 IP'; $flashOk = false; } else { $sql = 'DELETE il FROM ip_list il INNER JOIN ip_groups ig ON ig.id = il.group_id WHERE ig.type = ? AND il.ip_address = ?'; if ($scopeGroup > 0) { $sql .= ' AND il.group_id = ?'; } $stmt = $pdo->prepare($sql); $deleted = 0; $pdo->beginTransaction(); try { foreach ($ips as $ip) { if ($scopeGroup > 0) { $stmt->execute([$type, $ip, $scopeGroup]); } else { $stmt->execute([$type, $ip]); } $deleted += $stmt->rowCount(); } $pdo->commit(); } catch (Throwable $e) { $pdo->rollBack(); $flash = '批量删除失败:' . $e->getMessage(); $flashOk = false; } if ($flashOk) { $flash = '批量删除完成,共删除 ' . $deleted . ' 条记录(不存在的 IP 不计入)'; header('Location: ' . $self . '?type=' . urlencode($type) . '&msg=' . urlencode($flash) . '&ok=1'); exit; } } } } // —— POST:清空当前类型下所有 IP(保留分组)—— if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'clear_ips_only') { $phrase = trim((string) ($_POST['clear_confirm_phrase'] ?? '')); if ($phrase !== '清空IP') { $flash = '清空失败:请在确认框中完整输入「清空IP」'; $flashOk = false; } else { $del = $pdo->prepare( 'DELETE il FROM ip_list il INNER JOIN ip_groups ig ON ig.id = il.group_id WHERE ig.type = ?' ); $del->execute([$type]); $flash = '已清空当前类型下所有 IP(分组已保留),删除 ' . $del->rowCount() . ' 条'; header('Location: ' . $self . '?type=' . urlencode($type) . '&msg=' . urlencode($flash) . '&ok=1'); exit; } } // —— POST:清空当前类型下全部分组及 IP —— if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'clear_type_all') { $phrase = trim((string) ($_POST['clear_confirm_phrase_all'] ?? '')); if ($phrase !== '清空全部') { $flash = '清空失败:请在确认框中完整输入「清空全部」'; $flashOk = false; } else { $pdo->beginTransaction(); try { $pdo->prepare( 'DELETE il FROM ip_list il INNER JOIN ip_groups ig ON ig.id = il.group_id WHERE ig.type = ?' )->execute([$type]); $pdo->prepare('DELETE FROM ip_groups WHERE type = ?')->execute([$type]); $pdo->commit(); $flash = '已清空当前类型下的所有分组及 IP'; header('Location: ' . $self . '?type=' . urlencode($type) . '&msg=' . urlencode($flash) . '&ok=1'); exit; } catch (Throwable $e) { $pdo->rollBack(); $flash = '清空失败:' . $e->getMessage(); $flashOk = false; } } } // —— GET:删除分组 —— if (isset($_GET['delete_group'])) { $gid = (int) $_GET['delete_group']; if ($gid > 0 && groupBelongsToType($pdo, $gid, $type)) { $pdo->beginTransaction(); try { $pdo->prepare('DELETE FROM ip_list WHERE group_id = ?')->execute([$gid]); $pdo->prepare('DELETE FROM ip_groups WHERE id = ? AND type = ?')->execute([$gid, $type]); $pdo->commit(); $flash = '分组及其 IP 已删除'; } catch (Throwable $e) { $pdo->rollBack(); $flash = '删除分组失败'; $flashOk = false; } } else { $flash = '无效的分组'; $flashOk = false; } header('Location: ' . $self . '?type=' . urlencode($type) . '&msg=' . urlencode($flash) . ($flashOk ? '&ok=1' : '')); exit; } // —— GET:删除单条 IP —— if (isset($_GET['delete_ip'])) { $ipId = (int) $_GET['delete_ip']; if ($ipId > 0) { $del = $pdo->prepare( 'DELETE il FROM ip_list il INNER JOIN ip_groups ig ON ig.id = il.group_id WHERE il.id = ? AND ig.type = ?' ); $del->execute([$ipId, $type]); $flash = $del->rowCount() > 0 ? '已删除该 IP 记录' : '未找到记录或无权删除'; $flashOk = $del->rowCount() > 0; } else { $flash = '无效的 IP 记录'; $flashOk = false; } header('Location: ' . $self . '?type=' . urlencode($type) . '&msg=' . urlencode($flash) . ($flashOk ? '&ok=1' : '')); exit; } if (isset($_GET['msg'])) { $flash = (string) $_GET['msg']; $flashOk = isset($_GET['ok']) && $_GET['ok'] === '1'; } // 当前 type 下的分组 $groupsStmt = $pdo->prepare('SELECT id, name FROM ip_groups WHERE type = ? ORDER BY id ASC'); $groupsStmt->execute([$type]); $groups = $groupsStmt->fetchAll(); $ipSearch = isset($_GET['ip_search']) ? trim((string) $_GET['ip_search']) : ''; $ipSearchSql = ''; $ipListBind = [$type]; if ($ipSearch !== '') { $ipSearchSql = ' AND il.ip_address LIKE ?'; $ipListBind[] = '%' . addcslashes($ipSearch, '\\%_') . '%'; } $searchQuerySuffix = $ipSearch !== '' ? '&ip_search=' . rawurlencode($ipSearch) : ''; // IP 列表分页 $perPage = 50; $page = max(1, (int) ($_GET['page'] ?? 1)); $offset = ($page - 1) * $perPage; $countSql = 'SELECT COUNT(*) FROM ip_list il INNER JOIN ip_groups ig ON ig.id = il.group_id WHERE ig.type = ?' . $ipSearchSql; $totalStmt = $pdo->prepare($countSql); $totalStmt->execute($ipListBind); $totalIps = (int) $totalStmt->fetchColumn(); $totalPages = max(1, (int) ceil($totalIps / $perPage)); if ($page > $totalPages) { $page = $totalPages; $offset = ($page - 1) * $perPage; } $listSql = 'SELECT il.id, il.ip_address, il.group_id, ig.name AS group_name FROM ip_list il INNER JOIN ip_groups ig ON ig.id = il.group_id WHERE ig.type = ?' . $ipSearchSql . ' ORDER BY il.id DESC LIMIT ' . (int) $perPage . ' OFFSET ' . (int) $offset; $listStmt = $pdo->prepare($listSql); $listStmt->execute($ipListBind); $ipRows = $listStmt->fetchAll(); function h(string $s): string { return htmlspecialchars($s, ENT_QUOTES, 'UTF-8'); } $typeParam = 'type=' . urlencode($type); ?> IP 名单管理 — <?php echo $type === 'white' ? '白名单' : '黑名单'; ?>

IP 黑白名单管理

新建分组

当前视图下的分组

暂无分组,请先创建。

ID组名操作
删除组

批量导入 IP

每行一个 IP;首尾空格与空行忽略;同一文本内重复只计一次;本组数据库中已存在的 IP 不会重复插入。

批量删除 IP

每行一个 IP;同一 IP 在文本中多次出现会去重后删除。选择「不限定分组」时,会删除当前名单类型下所有分组中匹配的 IP。

清空数据(危险操作)

以下操作仅影响当前视图类型(),不影响另一类型。

IP 列表(

清除

条,每页

当前类型下暂无 IP。

IDIP分组操作
删除
1) { ?>
1) { ?> 上一页 / 下一页