242 lines
8.9 KiB
PHP
Executable File
242 lines
8.9 KiB
PHP
Executable File
<?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';
|
|
}
|
|
}
|