CloudFlare.php 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. <?php
  2. namespace App\Utils\DDNS;
  3. use App\Utils\Library\Templates\DNS;
  4. use Cache;
  5. use Http;
  6. use Log;
  7. use RuntimeException;
  8. class CloudFlare implements DNS
  9. {
  10. // 开发依据: https://developers.cloudflare.com/api/
  11. public const KEY = 'cloudflare';
  12. public const LABEL = 'CloudFlare';
  13. private string $apiEndpoint;
  14. private array $auth;
  15. public function __construct(private readonly string $subdomain)
  16. {
  17. $this->apiEndpoint = 'https://api.cloudflare.com/client/v4/zones/';
  18. $this->auth = ['X-Auth-Key' => sysConfig('ddns_secret'), 'X-Auth-Email' => sysConfig('ddns_key')];
  19. $zoneIdentifier = $this->getZoneIdentifier();
  20. if ($zoneIdentifier) {
  21. $this->apiEndpoint .= "$zoneIdentifier/dns_records/";
  22. }
  23. }
  24. private function getZoneIdentifier(): string
  25. {
  26. $zones = Cache::remember('ddns_get_domains', now()->addHour(), function () {
  27. return array_column($this->sendRequest('ListZones') ?? [], 'name', 'id');
  28. });
  29. foreach ($zones as $zoneID => $zoneName) {
  30. if (str_contains($this->subdomain, $zoneName)) {
  31. return $zoneID;
  32. }
  33. }
  34. throw new RuntimeException('['.self::LABEL." — ListZones] The subdomain $this->subdomain does not match any domain in your account.");
  35. }
  36. private function sendRequest(string $action, array $parameters = [], ?string $identifier = null): array
  37. {
  38. $client = Http::timeout(10)->retry(3, 1000)->withHeaders($this->auth)->baseUrl($this->apiEndpoint)->asJson();
  39. $response = match ($action) {
  40. 'ListZones' => $client->get(''),
  41. 'ListDNSRecords' => $client->get('', $parameters),
  42. 'CreateDNSRecord' => $client->post('', $parameters),
  43. 'UpdateDNSRecord' => $client->put($identifier, $parameters),
  44. 'DeleteDNSRecord' => $client->delete($identifier),
  45. };
  46. $data = $response->json();
  47. if ($data) {
  48. if ($data['success'] && $response->ok()) {
  49. return $data['result'] ?? [];
  50. }
  51. Log::error('['.self::LABEL." — $action] 返回错误信息: ".$data['errors'][0]['message'] ?? 'Unknown error');
  52. } else {
  53. Log::error('['.self::LABEL." — $action] 请求失败");
  54. }
  55. exit(400);
  56. }
  57. public function store(string $ip, string $type): bool
  58. {
  59. $result = $this->sendRequest('CreateDNSRecord', ['content' => $ip, 'name' => $this->subdomain, 'type' => $type]);
  60. return ! empty($result);
  61. }
  62. public function update(string $latest_ip, string $original_ip, string $type): bool
  63. {
  64. $recordIds = $this->getRecordIds($type, $original_ip);
  65. if ($recordIds) {
  66. $ret = $this->sendRequest('UpdateDNSRecord', ['content' => $latest_ip, 'name' => $this->subdomain, 'type' => $type], $recordIds[0]);
  67. }
  68. return (bool) ($ret ?? false);
  69. }
  70. private function getRecordIds(string $type, string $ip): array|false
  71. {
  72. $records = $this->sendRequest('ListDNSRecords', ['content' => $ip, 'name' => $this->subdomain, 'type' => $type]);
  73. if ($records) {
  74. return array_column($records, 'id');
  75. }
  76. return false;
  77. }
  78. public function destroy(string $type, string $ip): int
  79. {
  80. $recordIds = $this->getRecordIds($type, $ip);
  81. $deletedCount = 0;
  82. foreach ($recordIds as $recordId) {
  83. if ($this->sendRequest('DeleteDNSRecord', [], $recordId)) {
  84. $deletedCount++;
  85. }
  86. }
  87. return $deletedCount;
  88. }
  89. }