feat: initial commit
This commit is contained in:
Executable
+257
@@ -0,0 +1,257 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/DomainRepository.php';
|
||||
require_once __DIR__ . '/CloudflareConfig.php';
|
||||
require_once __DIR__ . '/CloudflareClient.php';
|
||||
|
||||
/**
|
||||
* 域名添加 / NS 检测 / DNS+SSL 配置 / 删除编排
|
||||
*/
|
||||
class DomainProvisioningService
|
||||
{
|
||||
/** @var CloudflareClient|null CLI 回归注入 Mock 客户端 */
|
||||
private static $clientOverride = null;
|
||||
|
||||
public static function setClientOverride($client)
|
||||
{
|
||||
self::$clientOverride = $client;
|
||||
}
|
||||
|
||||
public static function clearClientOverride()
|
||||
{
|
||||
self::$clientOverride = null;
|
||||
}
|
||||
|
||||
private static function newClient()
|
||||
{
|
||||
return self::$clientOverride !== null ? self::$clientOverride : new CloudflareClient();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加域名:本地入库 + 可选创建 Cloudflare Zone
|
||||
*
|
||||
* @return array{ok:bool,error?:string,hostname?:string,id?:int,nameservers?:array,cf_status?:string}
|
||||
*/
|
||||
public static function provisionOnAdd(PDO $pdo, $hostname)
|
||||
{
|
||||
$add = DomainRepository::add($pdo, $hostname);
|
||||
if (!$add['ok']) {
|
||||
return $add;
|
||||
}
|
||||
|
||||
$id = (int) $add['id'];
|
||||
$host = $add['hostname'];
|
||||
|
||||
if (!CloudflareConfig::isEnabled()) {
|
||||
DomainRepository::updateCloudflareMeta($pdo, $id, [
|
||||
'cf_status' => 'local',
|
||||
]);
|
||||
return [
|
||||
'ok' => true,
|
||||
'id' => $id,
|
||||
'hostname' => $host,
|
||||
'cf_status' => 'local',
|
||||
'nameservers' => [],
|
||||
];
|
||||
}
|
||||
|
||||
$client = self::newClient();
|
||||
$zone = $client->createZone($host);
|
||||
if (!$zone['ok']) {
|
||||
DomainRepository::updateCloudflareMeta($pdo, $id, [
|
||||
'cf_status' => 'error',
|
||||
'cf_error' => $zone['error'] ?? '创建 Zone 失败',
|
||||
]);
|
||||
return [
|
||||
'ok' => false,
|
||||
'error' => $zone['error'] ?? 'Cloudflare 创建 Zone 失败',
|
||||
'id' => $id,
|
||||
'hostname' => $host,
|
||||
];
|
||||
}
|
||||
|
||||
$result = $zone['result'];
|
||||
$ns = is_array($result['name_servers'] ?? null) ? $result['name_servers'] : [];
|
||||
DomainRepository::updateCloudflareMeta($pdo, $id, [
|
||||
'cf_zone_id' => (string) ($result['id'] ?? ''),
|
||||
'cf_nameservers' => json_encode($ns, JSON_UNESCAPED_UNICODE),
|
||||
'cf_status' => 'pending_ns',
|
||||
'cf_error' => '',
|
||||
'cf_checked_at' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
|
||||
return [
|
||||
'ok' => true,
|
||||
'id' => $id,
|
||||
'hostname' => $host,
|
||||
'cf_status' => 'pending_ns',
|
||||
'nameservers' => $ns,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动检测 NS 是否生效,生效后配置 A 记录与 SSL
|
||||
*
|
||||
* @return array{ok:bool,status?:string,message?:string,nameservers?:array}
|
||||
*/
|
||||
public static function checkAndProvision(PDO $pdo, $domainId)
|
||||
{
|
||||
$row = DomainRepository::findById($pdo, $domainId);
|
||||
if (!$row) {
|
||||
return ['ok' => false, 'message' => '域名不存在'];
|
||||
}
|
||||
|
||||
if (!CloudflareConfig::isEnabled()) {
|
||||
return ['ok' => false, 'message' => '未配置 Cloudflare API,无法检测'];
|
||||
}
|
||||
|
||||
$client = self::newClient();
|
||||
$zoneId = trim((string) ($row['cf_zone_id'] ?? ''));
|
||||
|
||||
if ($zoneId === '') {
|
||||
$zone = $client->createZone($row['hostname']);
|
||||
if (!$zone['ok']) {
|
||||
DomainRepository::updateCloudflareMeta($pdo, $domainId, [
|
||||
'cf_status' => 'error',
|
||||
'cf_error' => $zone['error'] ?? '补创建 Zone 失败',
|
||||
'cf_checked_at' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
return ['ok' => false, 'message' => $zone['error'] ?? '补创建 Zone 失败'];
|
||||
}
|
||||
$result = $zone['result'];
|
||||
$zoneId = (string) ($result['id'] ?? '');
|
||||
$ns = is_array($result['name_servers'] ?? null) ? $result['name_servers'] : [];
|
||||
DomainRepository::updateCloudflareMeta($pdo, $domainId, [
|
||||
'cf_zone_id' => $zoneId,
|
||||
'cf_nameservers' => json_encode($ns, JSON_UNESCAPED_UNICODE),
|
||||
'cf_status' => 'pending_ns',
|
||||
'cf_error' => '',
|
||||
'cf_checked_at' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
$row = DomainRepository::findById($pdo, $domainId);
|
||||
}
|
||||
|
||||
$zoneRes = $client->getZone($zoneId);
|
||||
if (!$zoneRes['ok']) {
|
||||
DomainRepository::updateCloudflareMeta($pdo, $domainId, [
|
||||
'cf_status' => 'error',
|
||||
'cf_error' => $zoneRes['error'] ?? '读取 Zone 失败',
|
||||
'cf_checked_at' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
return ['ok' => false, 'message' => $zoneRes['error'] ?? '读取 Zone 失败'];
|
||||
}
|
||||
|
||||
$zoneData = $zoneRes['result'];
|
||||
$status = strtolower((string) ($zoneData['status'] ?? 'pending'));
|
||||
$ns = CloudflareConfig::parseNameservers($row['cf_nameservers'] ?? '');
|
||||
if (empty($ns) && is_array($zoneData['name_servers'] ?? null)) {
|
||||
$ns = $zoneData['name_servers'];
|
||||
DomainRepository::updateCloudflareMeta($pdo, $domainId, [
|
||||
'cf_nameservers' => json_encode($ns, JSON_UNESCAPED_UNICODE),
|
||||
]);
|
||||
}
|
||||
|
||||
if ($status !== 'active') {
|
||||
DomainRepository::updateCloudflareMeta($pdo, $domainId, [
|
||||
'cf_status' => 'pending_ns',
|
||||
'cf_checked_at' => date('Y-m-d H:i:s'),
|
||||
'cf_error' => '',
|
||||
]);
|
||||
return [
|
||||
'ok' => true,
|
||||
'status' => 'pending_ns',
|
||||
'message' => 'Nameserver 尚未生效,请到注册商修改为:' . implode('、', $ns),
|
||||
'nameservers' => $ns,
|
||||
];
|
||||
}
|
||||
|
||||
$serverIp = CloudflareConfig::serverIp();
|
||||
if ($serverIp === '') {
|
||||
DomainRepository::updateCloudflareMeta($pdo, $domainId, [
|
||||
'cf_status' => 'error',
|
||||
'cf_error' => 'SERVER_IP 未配置,无法添加 DNS 记录',
|
||||
'cf_checked_at' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
return ['ok' => false, 'message' => 'SERVER_IP 未配置,请在 cong.php 中填写服务器公网 IP'];
|
||||
}
|
||||
|
||||
DomainRepository::updateCloudflareMeta($pdo, $domainId, [
|
||||
'cf_status' => 'provisioning',
|
||||
'cf_error' => '',
|
||||
]);
|
||||
|
||||
$host = $row['hostname'];
|
||||
foreach ([$host, 'www.' . $host] as $fqdn) {
|
||||
$dns = $client->upsertARecord($zoneId, $fqdn, $serverIp, true);
|
||||
if (!$dns['ok']) {
|
||||
DomainRepository::updateCloudflareMeta($pdo, $domainId, [
|
||||
'cf_status' => 'error',
|
||||
'cf_error' => 'DNS 记录失败(' . $fqdn . '): ' . ($dns['error'] ?? ''),
|
||||
'cf_checked_at' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
return ['ok' => false, 'message' => $dns['error'] ?? 'DNS 配置失败'];
|
||||
}
|
||||
}
|
||||
|
||||
$ssl = $client->setSslFlexible($zoneId);
|
||||
if (!$ssl['ok']) {
|
||||
DomainRepository::updateCloudflareMeta($pdo, $domainId, [
|
||||
'cf_status' => 'error',
|
||||
'cf_error' => 'SSL Flexible 失败: ' . ($ssl['error'] ?? ''),
|
||||
'cf_checked_at' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
return ['ok' => false, 'message' => $ssl['error'] ?? 'SSL 配置失败'];
|
||||
}
|
||||
|
||||
$https = $client->setAlwaysHttps($zoneId);
|
||||
if (!$https['ok']) {
|
||||
DomainRepository::updateCloudflareMeta($pdo, $domainId, [
|
||||
'cf_status' => 'error',
|
||||
'cf_error' => 'Always HTTPS 失败: ' . ($https['error'] ?? ''),
|
||||
'cf_checked_at' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
return ['ok' => false, 'message' => $https['error'] ?? 'Always HTTPS 配置失败'];
|
||||
}
|
||||
|
||||
DomainRepository::updateCloudflareMeta($pdo, $domainId, [
|
||||
'cf_status' => 'ready',
|
||||
'cf_error' => '',
|
||||
'cf_checked_at' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
|
||||
return [
|
||||
'ok' => true,
|
||||
'status' => 'ready',
|
||||
'message' => '域名已生效:A 记录(@/www) 已指向 ' . $serverIp . ',已开启 Flexible SSL 与 Always HTTPS',
|
||||
'nameservers' => $ns,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除本地记录并尝试删除 Cloudflare Zone
|
||||
*
|
||||
* @return array{ok:bool,message?:string,warning?:string}
|
||||
*/
|
||||
public static function deleteWithCloudflare(PDO $pdo, $domainId)
|
||||
{
|
||||
$row = DomainRepository::findById($pdo, $domainId);
|
||||
if (!$row) {
|
||||
return ['ok' => false, 'message' => '域名不存在'];
|
||||
}
|
||||
|
||||
$warning = '';
|
||||
$zoneId = trim((string) ($row['cf_zone_id'] ?? ''));
|
||||
if ($zoneId !== '' && CloudflareConfig::isEnabled()) {
|
||||
$del = self::newClient()->deleteZone($zoneId);
|
||||
if (!$del['ok']) {
|
||||
$warning = 'Cloudflare Zone 删除失败:' . ($del['error'] ?? '未知错误');
|
||||
}
|
||||
}
|
||||
|
||||
DomainRepository::deleteById($pdo, $domainId);
|
||||
return [
|
||||
'ok' => true,
|
||||
'message' => '域名已删除',
|
||||
'warning' => $warning,
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user