feat: initial commit

This commit is contained in:
root
2026-06-14 14:00:24 +08:00
commit 72d388f642
118 changed files with 39015 additions and 0 deletions
+241
View File
@@ -0,0 +1,241 @@
<?php
/**
* 站点域名注册表(dashboard 管理,ip_check 按 Host 解析配置)
*/
class DomainRepository
{
public static function ensureTable(PDO $pdo)
{
$pdo->exec(
"CREATE TABLE IF NOT EXISTS `cloak_site_domains` (
`id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`hostname` varchar(255) NOT NULL COMMENT '站点域名,不含协议与路径',
`config_name` varchar(64) NOT NULL DEFAULT '' COMMENT '关联 check_config 配置名',
`cf_zone_id` varchar(64) DEFAULT NULL COMMENT 'Cloudflare Zone ID',
`cf_nameservers` text DEFAULT NULL COMMENT 'Cloudflare NS JSON',
`cf_status` varchar(32) NOT NULL DEFAULT 'local' COMMENT 'Cloudflare 状态',
`cf_error` varchar(500) DEFAULT NULL COMMENT '最近错误',
`cf_checked_at` datetime DEFAULT NULL COMMENT '上次检测时间',
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
UNIQUE KEY `uk_hostname` (`hostname`),
KEY `idx_config_name` (`config_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci"
);
self::ensureCloudflareColumns($pdo);
}
private static function ensureCloudflareColumns(PDO $pdo)
{
$columns = [
'cf_zone_id' => "ADD COLUMN `cf_zone_id` varchar(64) DEFAULT NULL COMMENT 'Cloudflare Zone ID'",
'cf_nameservers' => "ADD COLUMN `cf_nameservers` text DEFAULT NULL COMMENT 'Cloudflare NS JSON'",
'cf_status' => "ADD COLUMN `cf_status` varchar(32) NOT NULL DEFAULT 'local' COMMENT 'Cloudflare 状态'",
'cf_error' => "ADD COLUMN `cf_error` varchar(500) DEFAULT NULL COMMENT '最近错误'",
'cf_checked_at' => "ADD COLUMN `cf_checked_at` datetime DEFAULT NULL COMMENT '上次检测时间'",
];
foreach ($columns as $name => $ddl) {
if (!self::columnExists($pdo, 'cloak_site_domains', $name)) {
$pdo->exec('ALTER TABLE `cloak_site_domains` ' . $ddl);
}
}
}
private static function columnExists(PDO $pdo, $table, $column)
{
$st = $pdo->prepare(
'SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? AND COLUMN_NAME = ?'
);
$st->execute([DB_NAME, $table, $column]);
return (int) $st->fetchColumn() > 0;
}
private static function selectColumns()
{
return 'id, hostname, config_name, cf_zone_id, cf_nameservers, cf_status, cf_error, cf_checked_at, created_at';
}
public static function pdo()
{
return new PDO(
'mysql:host=127.0.0.1;dbname=' . DB_NAME . ';charset=utf8mb4',
DB_USERNAME,
DB_PASSWORD,
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]
);
}
public static function normalizeHostname($host)
{
$host = trim((string) $host);
if ($host === '') {
return '';
}
if (strpos($host, '://') !== false) {
$parsed = parse_url($host, PHP_URL_HOST);
$host = $parsed ?: $host;
} else {
$host = preg_replace('#/.*$#', '', $host);
$host = preg_replace('#:\d+$#', '', $host);
}
$host = strtolower(trim($host, '.'));
if (str_starts_with($host, 'www.')) {
$host = substr($host, 4);
}
return $host;
}
public static function hostToConfigKey($host)
{
$host = self::normalizeHostname($host);
if ($host === '') {
return 'index';
}
$key = preg_replace('/[^a-z0-9_-]+/i', '_', $host);
$key = trim($key, '_');
return $key !== '' ? $key : 'index';
}
public static function listAll(PDO $pdo = null)
{
$pdo = $pdo ?: self::pdo();
self::ensureTable($pdo);
$st = $pdo->query('SELECT ' . self::selectColumns() . ' FROM cloak_site_domains ORDER BY hostname ASC');
return $st->fetchAll();
}
public static function findById(PDO $pdo, $id)
{
self::ensureTable($pdo);
$st = $pdo->prepare('SELECT ' . self::selectColumns() . ' FROM cloak_site_domains WHERE id = ? LIMIT 1');
$st->execute([(int) $id]);
$row = $st->fetch();
return $row ?: null;
}
public static function updateCloudflareMeta(PDO $pdo, $id, array $fields)
{
self::ensureTable($pdo);
$allowed = ['cf_zone_id', 'cf_nameservers', 'cf_status', 'cf_error', 'cf_checked_at'];
$sets = [];
$vals = [];
foreach ($allowed as $key) {
if (array_key_exists($key, $fields)) {
$sets[] = "`{$key}` = ?";
$vals[] = $fields[$key];
}
}
if ($sets === []) {
return false;
}
$vals[] = (int) $id;
$sql = 'UPDATE cloak_site_domains SET ' . implode(', ', $sets) . ' WHERE id = ?';
$st = $pdo->prepare($sql);
$st->execute($vals);
return $st->rowCount() > 0;
}
public static function findByHostname(PDO $pdo, $hostname)
{
self::ensureTable($pdo);
$host = self::normalizeHostname($hostname);
if ($host === '') {
return null;
}
$st = $pdo->prepare('SELECT ' . self::selectColumns() . ' FROM cloak_site_domains WHERE hostname = ? LIMIT 1');
$st->execute([$host]);
$row = $st->fetch();
return $row ?: null;
}
public static function add(PDO $pdo, $hostname)
{
self::ensureTable($pdo);
$host = self::normalizeHostname($hostname);
if ($host === '' || !preg_match('/^[a-z0-9]([a-z0-9.-]*[a-z0-9])?$/i', $host)) {
return ['ok' => false, 'error' => '域名格式无效'];
}
try {
$st = $pdo->prepare('INSERT INTO cloak_site_domains (hostname, config_name) VALUES (?, ?)');
$st->execute([$host, '']);
return ['ok' => true, 'id' => (int) $pdo->lastInsertId(), 'hostname' => $host];
} catch (PDOException $e) {
if ((int) $e->getCode() === 23000) {
return ['ok' => false, 'error' => '该域名已存在'];
}
throw $e;
}
}
public static function deleteById(PDO $pdo, $id)
{
self::ensureTable($pdo);
$st = $pdo->prepare('DELETE FROM cloak_site_domains WHERE id = ?');
$st->execute([(int) $id]);
return $st->rowCount() > 0;
}
public static function bindConfigToDomain(PDO $pdo, $configName, $domainId)
{
self::ensureTable($pdo);
$configName = trim($configName);
$domainId = (int) $domainId;
if ($configName === '' || $domainId <= 0) {
return false;
}
$pdo->prepare("UPDATE cloak_site_domains SET config_name = '' WHERE config_name = ?")->execute([$configName]);
$st = $pdo->prepare('UPDATE cloak_site_domains SET config_name = ? WHERE id = ?');
$st->execute([$configName, $domainId]);
return $st->rowCount() > 0;
}
public static function clearConfigBinding(PDO $pdo, $configName)
{
self::ensureTable($pdo);
$st = $pdo->prepare("UPDATE cloak_site_domains SET config_name = '' WHERE config_name = ?");
$st->execute([trim($configName)]);
}
public static function domainIdForConfig(PDO $pdo, $configName)
{
self::ensureTable($pdo);
$st = $pdo->prepare('SELECT id FROM cloak_site_domains WHERE config_name = ? ORDER BY id ASC LIMIT 1');
$st->execute([trim($configName)]);
$row = $st->fetch();
return $row ? (int) $row['id'] : 0;
}
public static function primaryHostnameForConfig(PDO $pdo, $configName)
{
self::ensureTable($pdo);
$st = $pdo->prepare('SELECT hostname FROM cloak_site_domains WHERE config_name = ? ORDER BY id ASC LIMIT 1');
$st->execute([trim($configName)]);
$row = $st->fetch();
return $row ? $row['hostname'] : '';
}
public static function resolveConfigNameForHost($host, $baseDir = null)
{
$baseDir = $baseDir ?: dirname(__DIR__, 2);
$norm = self::normalizeHostname($host);
if ($norm !== '') {
try {
$pdo = self::pdo();
$row = self::findByHostname($pdo, $norm);
if ($row && !empty($row['config_name'])) {
$name = trim($row['config_name']);
if (file_exists($baseDir . '/check_config/' . $name . '_config.php')) {
return $name;
}
}
} catch (Throwable $e) {
}
$key = self::hostToConfigKey($norm);
if (file_exists($baseDir . '/check_config/' . $key . '_config.php')) {
return $key;
}
}
return 'index';
}
}