mirror of
https://github.com/netcccyun/dnsmgr.git
synced 2026-05-02 11:56:27 +02:00
* Update RewriteRule in .htaccess for cleaner routing 修复Apache环境下路由重写规则 废弃旧版 index.php/$1 写法,改用兼容新版PHP的PATH_INFO传参方式 解决访问时报错 No input file specified. 问题 * Add files via upload 1.添加DCV 委派一键添加CNAME 2.添加证书验证方法和最低 TLS 版本 3.添加批量添加 修改 删除 4.修复华为云一键txt解析失败(我没其他dns, 其他的需关注) 5.Cloudflare增强改Cloudflare自定义主机名 * 1.添加快速解析 2.Cloudflare自定义主机名添加搜索功能 * Add files via upload 1.Cloudflare自定义主机名自动获取默认线路(支持所有dns,华为云退回之前) 2.优化手机上显示问题 3.一键添加 DCV 委派支持选择要写入的解析域名 * 优化手机显示 * 添加1. 批量 DCV 委派 2. 批量主机名 TXT 验证 3. 批量证书 TXT 验证 4. 批量刷新验证 1. 批量 DCV 委派 2. 批量主机名 TXT 验证 3. 批量证书 TXT 验证 4. 批量刷新验证 * 快速解析改名智能解析,添加已有解析记录和智能批量添加 * 快速解析改名智能解析,添加已有解析记录和智能批量添加 * 由于之前复制保存的,代码有些差异 * 修复已有解析记录的备注功能 * 备注按dns显示 * 修复记录值过长无法复制,优化显示 * 优化显示
621 lines
22 KiB
PHP
621 lines
22 KiB
PHP
<?php
|
|
|
|
namespace app\service;
|
|
|
|
use Exception;
|
|
|
|
class CloudflareEnhanceService
|
|
{
|
|
private string $email = '';
|
|
private string $apiKey = '';
|
|
private int $auth = 0;
|
|
private bool $proxy = false;
|
|
private string $accountId = '';
|
|
private string $baseUrl = 'https://api.cloudflare.com/client/v4';
|
|
|
|
public function __construct(array $config = [])
|
|
{
|
|
$this->email = trim((string)($config['email'] ?? ''));
|
|
$this->apiKey = preg_replace('/\s+/', '', trim((string)($config['apikey'] ?? '')));
|
|
$this->auth = isset($config['auth']) ? intval($config['auth']) : (preg_match('/^[0-9a-f]+$/i', $this->apiKey) ? 0 : 1);
|
|
$this->proxy = isset($config['proxy']) && strval($config['proxy']) === '1';
|
|
$this->accountId = trim((string)($config['account_id'] ?? ''));
|
|
}
|
|
|
|
public function isApiTokenAuth(): bool
|
|
{
|
|
return $this->auth === 1;
|
|
}
|
|
|
|
public function getConfiguredAccountId(): string
|
|
{
|
|
return $this->accountId;
|
|
}
|
|
|
|
public function getAccounts(): array
|
|
{
|
|
try {
|
|
return $this->paginate('/accounts', [], 50);
|
|
} catch (Exception $e) {
|
|
$this->throwActionError('获取账户列表', $e, 'Account:Read');
|
|
}
|
|
}
|
|
|
|
public function getDefaultAccountId(): string
|
|
{
|
|
try {
|
|
$accounts = $this->getAccounts();
|
|
if (!empty($accounts[0]['id'])) {
|
|
return trim((string)$accounts[0]['id']);
|
|
}
|
|
} catch (Exception $e) {
|
|
}
|
|
|
|
try {
|
|
$payload = $this->requestRaw('GET', '/zones', ['page' => 1, 'per_page' => 1]);
|
|
$first = $payload['result'][0] ?? [];
|
|
$accountId = trim((string)($first['account']['id'] ?? ''));
|
|
if ($accountId !== '') {
|
|
return $accountId;
|
|
}
|
|
} catch (Exception $e) {
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
public function getZone(string $zoneId): array
|
|
{
|
|
try {
|
|
return $this->requestResult('GET', '/zones/' . $zoneId);
|
|
} catch (Exception $e) {
|
|
$this->throwActionError('获取域名详情', $e, 'Zone:Read');
|
|
}
|
|
}
|
|
|
|
public function listCustomHostnames(string $zoneId): array
|
|
{
|
|
try {
|
|
return $this->paginate('/zones/' . $zoneId . '/custom_hostnames', [], 100);
|
|
} catch (Exception $e) {
|
|
$this->throwActionError('获取自定义主机名列表', $e, 'SSL and Certificates:Read');
|
|
}
|
|
}
|
|
|
|
public function getCustomHostname(string $zoneId, string $hostnameId): array
|
|
{
|
|
try {
|
|
return $this->requestResult('GET', '/zones/' . $zoneId . '/custom_hostnames/' . trim($hostnameId));
|
|
} catch (Exception $e) {
|
|
$this->throwActionError('获取自定义主机名详情', $e, 'SSL and Certificates:Read');
|
|
}
|
|
}
|
|
|
|
public function createCustomHostname(string $zoneId, string $hostname, ?string $customOriginServer = null, string $sslMethod = 'http', string $minTlsVersion = '1.0'): array
|
|
{
|
|
$hostname = $this->normalizeHostname($hostname);
|
|
$payload = [
|
|
'hostname' => $hostname,
|
|
'ssl' => [
|
|
'method' => $sslMethod === 'txt' ? 'txt' : 'http',
|
|
'type' => 'dv',
|
|
'settings' => [
|
|
'min_tls_version' => $minTlsVersion
|
|
]
|
|
],
|
|
];
|
|
$origin = trim((string)$customOriginServer);
|
|
if ($origin !== '') {
|
|
$payload['custom_origin_server'] = $this->normalizeHostname($origin);
|
|
}
|
|
|
|
try {
|
|
return $this->requestResult('POST', '/zones/' . $zoneId . '/custom_hostnames', [], $payload);
|
|
} catch (Exception $e) {
|
|
$this->throwActionError('创建自定义主机名', $e, 'SSL and Certificates:Write');
|
|
}
|
|
}
|
|
|
|
public function updateCustomHostname(string $zoneId, string $hostnameId, array $payload): array
|
|
{
|
|
if (isset($payload['custom_origin_server']) && $payload['custom_origin_server'] !== null) {
|
|
$payload['custom_origin_server'] = $this->normalizeHostname($payload['custom_origin_server']);
|
|
}
|
|
if (isset($payload['hostname']) && $payload['hostname'] !== null) {
|
|
$payload['hostname'] = $this->normalizeHostname($payload['hostname']);
|
|
}
|
|
|
|
try {
|
|
return $this->requestResult('PATCH', '/zones/' . $zoneId . '/custom_hostnames/' . trim($hostnameId), [], $payload);
|
|
} catch (Exception $e) {
|
|
$this->throwActionError('更新自定义主机名', $e, 'SSL and Certificates:Write');
|
|
}
|
|
}
|
|
|
|
public function deleteCustomHostname(string $zoneId, string $hostnameId): bool
|
|
{
|
|
try {
|
|
$this->requestResult('DELETE', '/zones/' . $zoneId . '/custom_hostnames/' . $hostnameId);
|
|
return true;
|
|
} catch (Exception $e) {
|
|
$this->throwActionError('删除自定义主机名', $e, 'SSL and Certificates:Write');
|
|
}
|
|
}
|
|
|
|
public function getFallbackOrigin(string $zoneId): string
|
|
{
|
|
try {
|
|
$result = $this->requestResult('GET', '/zones/' . $zoneId . '/custom_hostnames/fallback_origin', [], null, true);
|
|
if ($result === null) {
|
|
return '';
|
|
}
|
|
return trim((string)($result['origin'] ?? ''));
|
|
} catch (Exception $e) {
|
|
if ($e->getCode() === 404) {
|
|
return '';
|
|
}
|
|
$this->throwActionError('获取 Fallback Origin', $e, 'SSL and Certificates:Read');
|
|
}
|
|
}
|
|
|
|
public function updateFallbackOrigin(string $zoneId, string $origin): string
|
|
{
|
|
try {
|
|
$result = $this->requestResult('PUT', '/zones/' . $zoneId . '/custom_hostnames/fallback_origin', [], [
|
|
'origin' => $this->normalizeHostname($origin),
|
|
]);
|
|
return trim((string)($result['origin'] ?? $origin));
|
|
} catch (Exception $e) {
|
|
$this->throwActionError('更新 Fallback Origin', $e, 'SSL and Certificates:Write');
|
|
}
|
|
}
|
|
|
|
public function deleteFallbackOrigin(string $zoneId): bool
|
|
{
|
|
try {
|
|
$this->requestResult('DELETE', '/zones/' . $zoneId . '/custom_hostnames/fallback_origin', [], null, true);
|
|
return true;
|
|
} catch (Exception $e) {
|
|
if ($e->getCode() === 404) {
|
|
return true;
|
|
}
|
|
$this->throwActionError('删除 Fallback Origin', $e, 'SSL and Certificates:Write');
|
|
}
|
|
}
|
|
|
|
public function getDcvDelegationUuid(string $zoneId): string
|
|
{
|
|
try {
|
|
$result = $this->requestResult('GET', '/zones/' . $zoneId . '/dcv_delegation/uuid', [], null, true);
|
|
if ($result === null) {
|
|
return '';
|
|
}
|
|
return trim((string)($result['uuid'] ?? ''));
|
|
} catch (Exception $e) {
|
|
$this->throwActionError('获取 DCV 委派 UUID', $e, 'SSL and Certificates:Read');
|
|
}
|
|
}
|
|
|
|
public function listTunnels(string $accountId): array
|
|
{
|
|
$this->assertTunnelSupported();
|
|
try {
|
|
return $this->paginate('/accounts/' . $accountId . '/cfd_tunnel', ['is_deleted' => 'false'], 100);
|
|
} catch (Exception $e) {
|
|
$this->throwActionError('获取 Tunnel 列表', $e, 'Cloudflare Tunnel:Read');
|
|
}
|
|
}
|
|
|
|
public function createTunnel(string $accountId, string $name): array
|
|
{
|
|
$this->assertTunnelSupported();
|
|
try {
|
|
return $this->requestResult('POST', '/accounts/' . $accountId . '/cfd_tunnel', [], [
|
|
'name' => trim($name),
|
|
'tunnel_secret' => base64_encode(random_bytes(32)),
|
|
]);
|
|
} catch (Exception $e) {
|
|
$this->throwActionError('创建 Tunnel', $e, 'Cloudflare Tunnel:Write');
|
|
}
|
|
}
|
|
|
|
public function deleteTunnel(string $accountId, string $tunnelId): bool
|
|
{
|
|
$this->assertTunnelSupported();
|
|
try {
|
|
$this->requestResult('DELETE', '/accounts/' . $accountId . '/cfd_tunnel/' . $tunnelId);
|
|
return true;
|
|
} catch (Exception $e) {
|
|
$this->throwActionError('删除 Tunnel', $e, 'Cloudflare Tunnel:Write');
|
|
}
|
|
}
|
|
|
|
public function getTunnelToken(string $accountId, string $tunnelId): string
|
|
{
|
|
$this->assertTunnelSupported();
|
|
try {
|
|
$result = $this->requestResult('GET', '/accounts/' . $accountId . '/cfd_tunnel/' . $tunnelId . '/token');
|
|
if (is_string($result)) {
|
|
return $result;
|
|
}
|
|
return trim((string)($result['token'] ?? ''));
|
|
} catch (Exception $e) {
|
|
$this->throwActionError('获取 Tunnel Token', $e, 'Cloudflare Tunnel:Read');
|
|
}
|
|
}
|
|
|
|
public function getTunnelConfig(string $accountId, string $tunnelId): array
|
|
{
|
|
$this->assertTunnelSupported();
|
|
try {
|
|
$result = $this->requestResult('GET', '/accounts/' . $accountId . '/cfd_tunnel/' . $tunnelId . '/configurations', [], null, true);
|
|
return is_array($result) ? $result : [];
|
|
} catch (Exception $e) {
|
|
$this->throwActionError('获取 Tunnel 配置', $e, 'Cloudflare Tunnel:Read');
|
|
}
|
|
}
|
|
|
|
public function updateTunnelConfig(string $accountId, string $tunnelId, array $config): array
|
|
{
|
|
$this->assertTunnelSupported();
|
|
try {
|
|
return $this->requestResult('PUT', '/accounts/' . $accountId . '/cfd_tunnel/' . $tunnelId . '/configurations', [], [
|
|
'config' => $config,
|
|
]);
|
|
} catch (Exception $e) {
|
|
$this->throwActionError('更新 Tunnel 配置', $e, 'Cloudflare Tunnel:Write');
|
|
}
|
|
}
|
|
|
|
public function listCidrRoutes(string $accountId, ?string $tunnelId = null): array
|
|
{
|
|
$this->assertTunnelSupported();
|
|
$query = ['is_deleted' => 'false'];
|
|
if (!empty($tunnelId)) {
|
|
$query['tunnel_id'] = $tunnelId;
|
|
}
|
|
|
|
try {
|
|
return $this->paginate('/accounts/' . $accountId . '/teamnet/routes', $query, 100);
|
|
} catch (Exception $e) {
|
|
$this->throwActionError('获取 CIDR 路由列表', $e, 'Cloudflare Tunnel:Read');
|
|
}
|
|
}
|
|
|
|
public function createCidrRoute(string $accountId, string $tunnelId, string $network, ?string $comment = null, ?string $virtualNetworkId = null): array
|
|
{
|
|
$this->assertTunnelSupported();
|
|
$payload = [
|
|
'network' => trim($network),
|
|
'tunnel_id' => trim($tunnelId),
|
|
];
|
|
if (!empty($comment)) {
|
|
$payload['comment'] = trim($comment);
|
|
}
|
|
if (!empty($virtualNetworkId)) {
|
|
$payload['virtual_network_id'] = trim($virtualNetworkId);
|
|
}
|
|
|
|
try {
|
|
return $this->requestResult('POST', '/accounts/' . $accountId . '/teamnet/routes', [], $payload);
|
|
} catch (Exception $e) {
|
|
$this->throwActionError('创建 CIDR 路由', $e, 'Cloudflare Tunnel:Write');
|
|
}
|
|
}
|
|
|
|
public function deleteCidrRoute(string $accountId, string $routeId): bool
|
|
{
|
|
$this->assertTunnelSupported();
|
|
try {
|
|
$this->requestResult('DELETE', '/accounts/' . $accountId . '/teamnet/routes/' . $routeId);
|
|
return true;
|
|
} catch (Exception $e) {
|
|
$this->throwActionError('删除 CIDR 路由', $e, 'Cloudflare Tunnel:Write');
|
|
}
|
|
}
|
|
|
|
public function listHostnameRoutes(string $accountId, ?string $tunnelId = null): array
|
|
{
|
|
$this->assertTunnelSupported();
|
|
$query = ['is_deleted' => 'false'];
|
|
if (!empty($tunnelId)) {
|
|
$query['tunnel_id'] = $tunnelId;
|
|
}
|
|
|
|
try {
|
|
return $this->paginate('/accounts/' . $accountId . '/zerotrust/routes/hostname', $query, 100);
|
|
} catch (Exception $e) {
|
|
$this->throwActionError('获取主机名路由列表', $e, 'Cloudflare Tunnel:Read');
|
|
}
|
|
}
|
|
|
|
public function createHostnameRoute(string $accountId, string $tunnelId, string $hostname, ?string $comment = null): array
|
|
{
|
|
$this->assertTunnelSupported();
|
|
$payload = [
|
|
'hostname' => $this->normalizeHostname($hostname),
|
|
'tunnel_id' => trim($tunnelId),
|
|
];
|
|
if (!empty($comment)) {
|
|
$payload['comment'] = trim($comment);
|
|
}
|
|
|
|
try {
|
|
return $this->requestResult('POST', '/accounts/' . $accountId . '/zerotrust/routes/hostname', [], $payload);
|
|
} catch (Exception $e) {
|
|
$this->throwActionError('创建主机名路由', $e, 'Cloudflare Tunnel:Write');
|
|
}
|
|
}
|
|
|
|
public function deleteHostnameRoute(string $accountId, string $routeId): bool
|
|
{
|
|
$this->assertTunnelSupported();
|
|
try {
|
|
$this->requestResult('DELETE', '/accounts/' . $accountId . '/zerotrust/routes/hostname/' . $routeId);
|
|
return true;
|
|
} catch (Exception $e) {
|
|
$this->throwActionError('删除主机名路由', $e, 'Cloudflare Tunnel:Write');
|
|
}
|
|
}
|
|
|
|
public function upsertTunnelCnameRecord(string $zoneId, string $hostname, string $tunnelId): array
|
|
{
|
|
$zoneId = trim($zoneId);
|
|
$hostname = $this->normalizeHostname($hostname);
|
|
$target = trim($tunnelId) . '.cfargotunnel.com';
|
|
|
|
try {
|
|
$payload = $this->requestRaw('GET', '/zones/' . $zoneId . '/dns_records', [
|
|
'name' => $hostname,
|
|
'type' => 'CNAME',
|
|
'page' => 1,
|
|
'per_page' => 100,
|
|
]);
|
|
$records = $payload['result'] ?? [];
|
|
|
|
$allByNamePayload = $this->requestRaw('GET', '/zones/' . $zoneId . '/dns_records', [
|
|
'name' => $hostname,
|
|
'page' => 1,
|
|
'per_page' => 100,
|
|
]);
|
|
$allByName = $allByNamePayload['result'] ?? [];
|
|
$otherTypes = [];
|
|
foreach ($allByName as $row) {
|
|
$type = strtoupper((string)($row['type'] ?? ''));
|
|
$name = $this->normalizeHostname($row['name'] ?? '');
|
|
if ($name === $hostname && $type !== 'CNAME') {
|
|
$otherTypes[] = $type;
|
|
}
|
|
}
|
|
if (!empty($otherTypes)) {
|
|
$otherTypes = array_unique(array_filter($otherTypes));
|
|
throw new Exception('主机名已存在非 CNAME 记录(' . implode(', ', $otherTypes) . '),无法同步 Tunnel CNAME', 400);
|
|
}
|
|
|
|
foreach ($records as $record) {
|
|
$name = $this->normalizeHostname($record['name'] ?? '');
|
|
if ($name !== $hostname) {
|
|
continue;
|
|
}
|
|
$content = $this->normalizeHostname($record['content'] ?? '');
|
|
$proxied = !empty($record['proxied']);
|
|
if ($content === $this->normalizeHostname($target) && $proxied) {
|
|
return ['action' => 'unchanged'];
|
|
}
|
|
|
|
$this->requestResult('PUT', '/zones/' . $zoneId . '/dns_records/' . $record['id'], [], [
|
|
'type' => 'CNAME',
|
|
'name' => $hostname,
|
|
'content' => $target,
|
|
'proxied' => true,
|
|
'ttl' => 1,
|
|
]);
|
|
return ['action' => 'updated'];
|
|
}
|
|
|
|
$this->requestResult('POST', '/zones/' . $zoneId . '/dns_records', [], [
|
|
'type' => 'CNAME',
|
|
'name' => $hostname,
|
|
'content' => $target,
|
|
'proxied' => true,
|
|
'ttl' => 1,
|
|
]);
|
|
return ['action' => 'created'];
|
|
} catch (Exception $e) {
|
|
$this->throwActionError('同步 Tunnel CNAME 记录', $e, 'Zone:DNS:Edit');
|
|
}
|
|
}
|
|
|
|
public function deleteTunnelCnameRecordIfMatch(string $zoneId, string $hostname, string $tunnelId): array
|
|
{
|
|
$zoneId = trim($zoneId);
|
|
$hostname = $this->normalizeHostname($hostname);
|
|
$target = $this->normalizeHostname(trim($tunnelId) . '.cfargotunnel.com');
|
|
|
|
try {
|
|
$payload = $this->requestRaw('GET', '/zones/' . $zoneId . '/dns_records', [
|
|
'name' => $hostname,
|
|
'type' => 'CNAME',
|
|
'page' => 1,
|
|
'per_page' => 100,
|
|
]);
|
|
$records = $payload['result'] ?? [];
|
|
foreach ($records as $record) {
|
|
$name = $this->normalizeHostname($record['name'] ?? '');
|
|
$content = $this->normalizeHostname($record['content'] ?? '');
|
|
if ($name === $hostname && $content === $target) {
|
|
$this->requestResult('DELETE', '/zones/' . $zoneId . '/dns_records/' . $record['id']);
|
|
return ['deleted' => true];
|
|
}
|
|
}
|
|
return ['deleted' => false];
|
|
} catch (Exception $e) {
|
|
$this->throwActionError('删除 Tunnel CNAME 记录', $e, 'Zone:DNS:Edit');
|
|
}
|
|
}
|
|
|
|
private function paginate(string $path, array $query = [], int $perPage = 100): array
|
|
{
|
|
$all = [];
|
|
$page = 1;
|
|
$maxPage = 200;
|
|
while ($page <= $maxPage) {
|
|
$payload = $this->requestRaw('GET', $path, array_merge($query, [
|
|
'page' => $page,
|
|
'per_page' => $perPage,
|
|
]));
|
|
$batch = $payload['result'] ?? [];
|
|
if (!is_array($batch)) {
|
|
$batch = [];
|
|
}
|
|
foreach ($batch as $item) {
|
|
$all[] = $item;
|
|
}
|
|
|
|
$totalPages = intval($payload['result_info']['total_pages'] ?? 0);
|
|
if ($totalPages > 0) {
|
|
if ($page >= $totalPages) {
|
|
break;
|
|
}
|
|
} elseif (count($batch) < $perPage || empty($batch)) {
|
|
break;
|
|
}
|
|
$page++;
|
|
}
|
|
return $all;
|
|
}
|
|
|
|
private function requestResult(string $method, string $path, array $query = [], ?array $body = null, bool $allowNotFound = false)
|
|
{
|
|
$payload = $this->requestRaw($method, $path, $query, $body, $allowNotFound);
|
|
if ($payload === null) {
|
|
return null;
|
|
}
|
|
return $payload['result'] ?? [];
|
|
}
|
|
|
|
private function requestRaw(string $method, string $path, array $query = [], ?array $body = null, bool $allowNotFound = false): ?array
|
|
{
|
|
$headers = $this->buildHeaders($body !== null);
|
|
$url = $this->baseUrl . $path;
|
|
if (!empty($query)) {
|
|
$url .= '?' . http_build_query($query);
|
|
}
|
|
|
|
$response = http_request(
|
|
$url,
|
|
$body,
|
|
null,
|
|
null,
|
|
$headers,
|
|
$this->proxy,
|
|
strtoupper($method),
|
|
20
|
|
);
|
|
|
|
$status = intval($response['code'] ?? 0);
|
|
if ($allowNotFound && $status === 404) {
|
|
return null;
|
|
}
|
|
|
|
$payload = json_decode($response['body'] ?? '', true);
|
|
if (!is_array($payload)) {
|
|
throw new Exception('Cloudflare 返回数据解析失败', $status > 0 ? $status : 502);
|
|
}
|
|
|
|
if (($payload['success'] ?? false) !== true) {
|
|
if ($allowNotFound && $status === 404) {
|
|
return null;
|
|
}
|
|
$message = $this->extractErrorMessage($payload);
|
|
throw new Exception($message !== '' ? $message : 'Cloudflare API 请求失败', $status > 0 ? $status : 400);
|
|
}
|
|
|
|
return $payload;
|
|
}
|
|
|
|
private function buildHeaders(bool $json = false): array
|
|
{
|
|
if ($this->apiKey === '') {
|
|
throw new Exception('Cloudflare API 凭证为空', 400);
|
|
}
|
|
|
|
if ($this->auth === 1) {
|
|
$headers = [
|
|
'Authorization' => 'Bearer ' . $this->apiKey,
|
|
];
|
|
} else {
|
|
if ($this->email === '') {
|
|
throw new Exception('当前 Cloudflare 账户缺少邮箱地址,旧版 API Key 认证需要填写邮箱', 400);
|
|
}
|
|
$headers = [
|
|
'X-Auth-Email' => $this->email,
|
|
'X-Auth-Key' => $this->apiKey,
|
|
];
|
|
}
|
|
|
|
if ($json) {
|
|
$headers['Content-Type'] = 'application/json';
|
|
}
|
|
|
|
return $headers;
|
|
}
|
|
|
|
private function assertTunnelSupported(): void
|
|
{
|
|
if (!$this->isApiTokenAuth()) {
|
|
throw new Exception('Cloudflare Tunnels 仅支持 API 令牌认证,请将当前账户的认证方式切换为 API令牌', 400);
|
|
}
|
|
}
|
|
|
|
private function normalizeHostname($hostname): string
|
|
{
|
|
$hostname = trim((string)$hostname);
|
|
if ($hostname === '') {
|
|
return '';
|
|
}
|
|
$hostname = rtrim($hostname, '.');
|
|
$hostname = convertDomainToAscii($hostname);
|
|
return strtolower($hostname);
|
|
}
|
|
|
|
private function extractErrorMessage(array $payload): string
|
|
{
|
|
if (!empty($payload['errors'][0]['message'])) {
|
|
return trim((string)$payload['errors'][0]['message']);
|
|
}
|
|
if (!empty($payload['messages'][0]['message'])) {
|
|
return trim((string)$payload['messages'][0]['message']);
|
|
}
|
|
if (!empty($payload['result']['message'])) {
|
|
return trim((string)$payload['result']['message']);
|
|
}
|
|
return '';
|
|
}
|
|
|
|
private function throwActionError(string $action, Exception $e, string $permissionHint = ''): void
|
|
{
|
|
$status = intval($e->getCode());
|
|
$message = trim($e->getMessage());
|
|
|
|
if ($status === 401) {
|
|
$message = 'Cloudflare 凭证无效或已过期,无法' . $action;
|
|
} elseif ($status === 403) {
|
|
$message = 'Cloudflare 权限不足,无法' . $action;
|
|
if ($permissionHint !== '') {
|
|
$message .= '。请确认 Token 具备 ' . $permissionHint . ' 权限';
|
|
}
|
|
} elseif ($status === 404 && $message === '') {
|
|
$message = $action . '失败:资源不存在';
|
|
} elseif ($status === 429) {
|
|
$message = 'Cloudflare API 请求过于频繁,暂时无法' . $action . ',请稍后重试';
|
|
} elseif ($status >= 500) {
|
|
$message = 'Cloudflare 服务暂时不可用,无法' . $action . ',请稍后重试';
|
|
} elseif ($message === '') {
|
|
$message = $action . '失败';
|
|
}
|
|
|
|
throw new Exception($message, $status > 0 ? $status : 400);
|
|
}
|
|
}
|