手动添加域名

This commit is contained in:
root
2026-07-06 10:34:34 +08:00
parent c3c58198ab
commit 7efb3e6eb5
11 changed files with 728 additions and 141 deletions
+1
View File
@@ -50,6 +50,7 @@ class CloudflareConfig
{
$map = [
'local' => '本地登记',
'manual' => '手动解析',
'pending_ns' => '待检测',
'provisioning' => '配置中',
'ready' => '已生效',
+62 -10
View File
@@ -29,27 +29,33 @@ class DomainProvisioningService
/**
* 添加域名:本地入库 + 可选创建 Cloudflare Zone
*
* @return array{ok:bool,error?:string,hostname?:string,id?:int,nameservers?:array,cf_status?:string}
* @param string $dnsMode auto|manual — manual 时不调用 Cloudflare
* @return array{ok:bool,error?:string,hostname?:string,id?:int,nameservers?:array,cf_status?:string,dns_mode?:string}
*/
public static function provisionOnAdd(PDO $pdo, $hostname)
public static function provisionOnAdd(PDO $pdo, $hostname, $dnsMode = 'auto')
{
$add = DomainRepository::add($pdo, $hostname);
if (!$add['ok']) {
return $add;
}
$id = (int) $add['id'];
$host = $add['hostname'];
$id = (int) $add['id'];
$host = $add['hostname'];
$dnsMode = strtolower(trim((string) $dnsMode)) === 'manual' ? 'manual' : 'auto';
if (!CloudflareConfig::isEnabled()) {
if ($dnsMode === 'manual' || !CloudflareConfig::isEnabled()) {
$status = $dnsMode === 'manual' ? 'manual' : 'local';
DomainRepository::updateCloudflareMeta($pdo, $id, [
'cf_status' => 'local',
'cf_status' => $status,
'cf_error' => '',
]);
return [
'ok' => true,
'id' => $id,
'hostname' => $host,
'cf_status' => 'local',
'ok' => true,
'id' => $id,
'hostname' => $host,
'cf_status' => $status,
'dns_mode' => $dnsMode,
'nameservers' => [],
];
}
@@ -84,10 +90,52 @@ class DomainProvisioningService
'id' => $id,
'hostname' => $host,
'cf_status' => 'pending_ns',
'dns_mode' => 'auto',
'nameservers' => $ns,
];
}
/**
* 批量添加域名
*
* @return array{ok:bool,error?:string,added?:array,failed?:array,dns_mode?:string}
*/
public static function provisionBatch(PDO $pdo, $hostnameText, $dnsMode = 'auto')
{
$hosts = DomainRepository::parseHostnameBatch($hostnameText);
if ($hosts === []) {
return ['ok' => false, 'error' => '请至少输入一个有效域名(每行一个)'];
}
$dnsMode = strtolower(trim((string) $dnsMode)) === 'manual' ? 'manual' : 'auto';
$added = [];
$failed = [];
foreach ($hosts as $host) {
$res = self::provisionOnAdd($pdo, $host, $dnsMode);
if (!empty($res['ok'])) {
$added[] = $res;
} else {
$failed[] = [
'hostname' => $host,
'error' => $res['error'] ?? '添加失败',
];
}
}
if ($added === []) {
$firstErr = $failed[0]['error'] ?? '添加失败';
return ['ok' => false, 'error' => $firstErr, 'added' => [], 'failed' => $failed, 'dns_mode' => $dnsMode];
}
return [
'ok' => true,
'added' => $added,
'failed' => $failed,
'dns_mode' => $dnsMode,
];
}
/**
* 手动检测 NS 是否生效,生效后配置 A 记录与 SSL
*
@@ -100,6 +148,10 @@ class DomainProvisioningService
return ['ok' => false, 'message' => '域名不存在'];
}
if (($row['cf_status'] ?? '') === 'manual') {
return ['ok' => false, 'message' => '该域名为手动解析模式,无需 Cloudflare 检测'];
}
if (!CloudflareConfig::isEnabled()) {
return ['ok' => false, 'message' => '未配置 Cloudflare API,无法检测'];
}
+155 -13
View File
@@ -23,6 +23,50 @@ class DomainRepository
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci"
);
self::ensureCloudflareColumns($pdo);
self::ensureConfigDomainMapTable($pdo);
}
/**
* 配置 → 域名(多配置可共用同一域名)
*/
public static function ensureConfigDomainMapTable(PDO $pdo)
{
$pdo->exec(
"CREATE TABLE IF NOT EXISTS `cloak_config_domain` (
`config_name` varchar(64) NOT NULL COMMENT 'check_config 配置名',
`domain_id` int(11) UNSIGNED NOT NULL COMMENT 'cloak_site_domains.id',
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`config_name`),
KEY `idx_domain_id` (`domain_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci"
);
self::migrateLegacyDomainBindings($pdo);
}
/**
* 将 cloak_site_domains.config_name 旧数据迁入 cloak_config_domain(幂等)
*/
private static function migrateLegacyDomainBindings(PDO $pdo)
{
$st = $pdo->query(
"SELECT id, config_name FROM cloak_site_domains WHERE config_name IS NOT NULL AND config_name != ''"
);
if (!$st) {
return;
}
$ins = $pdo->prepare(
'INSERT IGNORE INTO cloak_config_domain (config_name, domain_id) VALUES (?, ?)'
);
$clr = $pdo->prepare("UPDATE cloak_site_domains SET config_name = '' WHERE id = ?");
while ($row = $st->fetch()) {
$cfg = trim((string) ($row['config_name'] ?? ''));
$id = (int) ($row['id'] ?? 0);
if ($cfg === '' || $id <= 0) {
continue;
}
$ins->execute([$cfg, $id]);
$clr->execute([$id]);
}
}
private static function ensureCloudflareColumns(PDO $pdo)
@@ -86,6 +130,35 @@ class DomainRepository
return $host;
}
/**
* 从多行文本解析域名列表(每行一个,也支持逗号/分号分隔)
*
* @return string[]
*/
public static function parseHostnameBatch($text)
{
$text = str_replace(["\r\n", "\r"], "\n", trim((string) $text));
if ($text === '') {
return [];
}
$parts = preg_split('/[\n,;]+/', $text);
$seen = [];
$out = [];
foreach ($parts as $part) {
$host = self::normalizeHostname(trim($part));
if ($host === '' || isset($seen[$host])) {
continue;
}
if (!preg_match('/^[a-z0-9]([a-z0-9.-]*[a-z0-9])?$/i', $host)) {
continue;
}
$seen[$host] = true;
$out[] = $host;
}
return $out;
}
public static function hostToConfigKey($host)
{
$host = self::normalizeHostname($host);
@@ -171,8 +244,10 @@ class DomainRepository
public static function deleteById(PDO $pdo, $id)
{
self::ensureTable($pdo);
$id = (int) $id;
$pdo->prepare('DELETE FROM cloak_config_domain WHERE domain_id = ?')->execute([$id]);
$st = $pdo->prepare('DELETE FROM cloak_site_domains WHERE id = ?');
$st->execute([(int) $id]);
$st->execute([$id]);
return $st->rowCount() > 0;
}
@@ -184,35 +259,98 @@ class DomainRepository
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 = ?');
if (self::findById($pdo, $domainId) === null) {
return false;
}
$st = $pdo->prepare(
'INSERT INTO cloak_config_domain (config_name, domain_id) VALUES (?, ?)
ON DUPLICATE KEY UPDATE domain_id = VALUES(domain_id)'
);
$st->execute([$configName, $domainId]);
return $st->rowCount() > 0;
return true;
}
public static function clearConfigBinding(PDO $pdo, $configName)
{
self::ensureTable($pdo);
$st = $pdo->prepare("UPDATE cloak_site_domains SET config_name = '' WHERE config_name = ?");
$st = $pdo->prepare('DELETE FROM cloak_config_domain 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 = $pdo->prepare('SELECT domain_id FROM cloak_config_domain WHERE config_name = ? LIMIT 1');
$st->execute([trim($configName)]);
$row = $st->fetch();
return $row ? (int) $row['id'] : 0;
return $row ? (int) $row['domain_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 = $pdo->prepare(
'SELECT d.hostname FROM cloak_config_domain m
INNER JOIN cloak_site_domains d ON d.id = m.domain_id
WHERE m.config_name = ? LIMIT 1'
);
$st->execute([trim($configName)]);
$row = $st->fetch();
return $row ? $row['hostname'] : '';
return $row ? (string) $row['hostname'] : '';
}
/**
* @return array<int,string[]>
*/
public static function groupConfigsByDomainId(PDO $pdo)
{
self::ensureTable($pdo);
$st = $pdo->query('SELECT domain_id, config_name FROM cloak_config_domain ORDER BY config_name ASC');
$map = [];
while ($row = $st->fetch()) {
$did = (int) ($row['domain_id'] ?? 0);
if ($did <= 0) {
continue;
}
$map[$did][] = (string) $row['config_name'];
}
return $map;
}
/**
* @return string[]
*/
public static function configNamesForDomainId(PDO $pdo, $domainId)
{
self::ensureTable($pdo);
$st = $pdo->prepare('SELECT config_name FROM cloak_config_domain WHERE domain_id = ? ORDER BY config_name ASC');
$st->execute([(int) $domainId]);
$names = [];
while ($row = $st->fetch()) {
$names[] = (string) $row['config_name'];
}
return $names;
}
/**
* 从全部站点域名中随机选取一个(允许多配置共用同一域名)
*/
public static function pickRandomDomainId(PDO $pdo, $configName = '')
{
self::ensureTable($pdo);
unset($configName);
$all = self::listAll($pdo);
if ($all === []) {
return 0;
}
$pick = $all[array_rand($all)];
return (int) ($pick['id'] ?? 0);
}
public static function resolveConfigNameForHost($host, $baseDir = null)
@@ -222,12 +360,15 @@ class DomainRepository
if ($norm !== '') {
try {
$pdo = self::pdo();
self::ensureTable($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;
if ($row) {
$key = self::hostToConfigKey($norm);
if (file_exists($baseDir . '/check_config/' . $key . '_config.php')) {
return $key;
}
return 'index';
}
} catch (Throwable $e) {
}
@@ -236,6 +377,7 @@ class DomainRepository
return $key;
}
}
return 'index';
}
}
+5 -2
View File
@@ -157,8 +157,11 @@ class VisitorSimulationRunner
$pdo = DomainRepository::pdo();
DomainRepository::ensureTable($pdo);
$domRow = DomainRepository::findByHostname($pdo, $domain);
if ($domRow && !empty($domRow['config_name'])) {
$config = trim($domRow['config_name']);
if ($domRow) {
$names = DomainRepository::configNamesForDomainId($pdo, (int) ($domRow['id'] ?? 0));
if (count($names) === 1) {
$config = $names[0];
}
}
} catch (Throwable $e) {
}