AliYun.php 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. <?php
  2. namespace App\Utils\DDNS;
  3. use App\Utils\Library\Templates\DNS;
  4. use Arr;
  5. use Http;
  6. use Log;
  7. class AliYun implements DNS
  8. {
  9. // 开发依据: https://api.aliyun.com/document/Alidns/2015-01-09/overview
  10. private const API_HOST = 'https://alidns.aliyuncs.com/';
  11. private string $accessKeyID;
  12. private string $accessKeySecret;
  13. public function __construct(private readonly string $subDomain)
  14. {
  15. $this->accessKeyID = sysConfig('ddns_key');
  16. $this->accessKeySecret = sysConfig('ddns_secret');
  17. }
  18. public function store(string $ip, string $type): bool
  19. {
  20. $domainInfo = $this->analysisDomain();
  21. if ($domainInfo) {
  22. $ret = $this->send('AddDomainRecord', ['DomainName' => $domainInfo['host'], 'RR' => $domainInfo['rr'], 'Type' => $type, 'Value' => $ip]);
  23. }
  24. return (bool) ($ret ?? false);
  25. }
  26. private function analysisDomain(): array|false
  27. {
  28. $domains = data_get($this->send('DescribeDomains'), 'Domains.Domain.*.DomainName');
  29. if ($domains) {
  30. foreach ($domains as $domain) {
  31. if (str_contains($this->subDomain, $domain)) {
  32. return ['rr' => rtrim(substr($this->subDomain, 0, -strlen($domain)), '.'), 'host' => $domain];
  33. }
  34. }
  35. Log::error('[AliYun - DescribeDomains] 错误域名 '.$this->subDomain.' 不在账号拥有域名里');
  36. }
  37. return false;
  38. }
  39. private function send(string $action, array $info = []): array
  40. {
  41. $public = [
  42. 'Format' => 'JSON',
  43. 'Version' => '2015-01-09',
  44. 'AccessKeyId' => $this->accessKeyID,
  45. 'SignatureMethod' => 'HMAC-SHA1',
  46. 'Timestamp' => gmdate("Y-m-d\TH:i:s\Z"), //公共参数Timestamp GMT时间
  47. 'SignatureVersion' => '1.0',
  48. 'SignatureNonce' => str_replace('.', '', microtime(true)), //唯一数,用于防止网络重放攻击
  49. ];
  50. $parameters = array_merge(['Action' => $action], $public, $info);
  51. $parameters['Signature'] = $this->computeSignature($parameters);
  52. $response = Http::asForm()->timeout(15)->post(self::API_HOST, $parameters);
  53. $data = $response->json();
  54. if ($data) {
  55. if ($response->ok()) {
  56. return Arr::except($data, ['TotalCount', 'PageSize', 'RequestId', 'PageNumber']);
  57. }
  58. Log::error('[AliYun - '.$action.'] 返回错误信息:'.$data['Message']);
  59. } else {
  60. Log::error('[AliYun - '.$action.'] 请求失败');
  61. }
  62. exit(400);
  63. }
  64. private function computeSignature(array $parameters): string
  65. { // 签名
  66. ksort($parameters, SORT_STRING);
  67. $stringToBeSigned = 'POST&%2F&'.urlencode(http_build_query($parameters));
  68. return base64_encode(hash_hmac('sha1', $stringToBeSigned, $this->accessKeySecret.'&', true));
  69. }
  70. public function update(string $latest_ip, string $original_ip, string $type): bool
  71. {
  72. $records = $this->getRecordId($type, $original_ip);
  73. $domainInfo = $this->analysisDomain();
  74. if ($records && $domainInfo) {
  75. $ret = $this->send('UpdateDomainRecord', ['RR' => $domainInfo['rr'], 'RecordId' => Arr::first($records), 'Type' => $type, 'Value' => $latest_ip]);
  76. }
  77. return (bool) ($ret ?? false);
  78. }
  79. private function getRecordId(string $type, string $ip): array|false
  80. { // 域名信息
  81. $parameters = ['SubDomain' => $this->subDomain];
  82. if ($type) {
  83. $parameters['Type'] = $type;
  84. }
  85. $records = $this->send('DescribeSubDomainRecords', $parameters);
  86. if ($records) {
  87. $filtered = data_get($records, 'DomainRecords.Record');
  88. if ($ip) {
  89. $filtered = Arr::where($filtered, static function (array $value) use ($ip) {
  90. return $value['Value'] === $ip;
  91. });
  92. }
  93. return data_get($filtered, '*.RecordId');
  94. }
  95. return false;
  96. }
  97. public function destroy(string $type = '', string $ip = ''): int
  98. {
  99. $records = $this->getRecordId($type, $ip);
  100. $count = 0;
  101. if ($records) {
  102. foreach ($records as $record) {
  103. if ($this->send('DeleteDomainRecord', ['RecordId' => $record])) {
  104. $count++;
  105. }
  106. }
  107. }
  108. return $count;
  109. }
  110. }