|
@@ -4,128 +4,153 @@ namespace App\Utils\DDNS;
|
|
|
|
|
|
use App\Utils\Library\Templates\DNS;
|
|
|
use Arr;
|
|
|
+use Cache;
|
|
|
use Http;
|
|
|
use Log;
|
|
|
+use RuntimeException;
|
|
|
|
|
|
class DNSPod implements DNS
|
|
|
{
|
|
|
// 开发依据: https://docs.dnspod.cn/api/
|
|
|
- private const API_HOST = 'https://dnsapi.cn/';
|
|
|
+ private const API_ENDPOINT = 'https://dnspod.tencentcloudapi.com';
|
|
|
|
|
|
- private string $loginToken;
|
|
|
+ private string $secretId;
|
|
|
|
|
|
- private array $domainData;
|
|
|
+ private string $secretKey;
|
|
|
|
|
|
- public function __construct(private readonly string $subDomain)
|
|
|
- {
|
|
|
- $this->loginToken = sysConfig('ddns_key').','.sysConfig('ddns_secret');
|
|
|
+ private array $domainInfo;
|
|
|
|
|
|
- $data = $this->analysisDomain();
|
|
|
- if ($data) {
|
|
|
- $this->domainData = $data;
|
|
|
- } else {
|
|
|
- abort(400, '域名存在异常');
|
|
|
- }
|
|
|
+ public function __construct(private readonly string $subdomain)
|
|
|
+ {
|
|
|
+ $this->secretId = sysConfig('ddns_key');
|
|
|
+ $this->secretKey = sysConfig('ddns_secret');
|
|
|
+ $this->domainInfo = $this->parseDomainInfo();
|
|
|
}
|
|
|
|
|
|
- private function analysisDomain(): array
|
|
|
+ private function parseDomainInfo(): array
|
|
|
{
|
|
|
- $domains = data_get($this->send('Domain.List', ['type' => 'mine']), 'domains.*.name');
|
|
|
+ $domains = Cache::remember('ddns_get_domains', now()->addHour(), function () {
|
|
|
+ return array_column($this->sendRequest('DescribeDomainList', ['Type' => 'mine'])['DomainList'], 'Name');
|
|
|
+ });
|
|
|
+
|
|
|
if ($domains) {
|
|
|
- foreach ($domains as $domain) {
|
|
|
- if (str_contains($this->subDomain, $domain)) {
|
|
|
- return ['rr' => rtrim(substr($this->subDomain, 0, -strlen($domain)), '.'), 'host' => $domain];
|
|
|
- }
|
|
|
- }
|
|
|
- Log::error('[DNS] DNSPod - 错误域名 '.$this->subDomain.' 不在账号拥有域名里');
|
|
|
+ $matched = Arr::first($domains, fn ($domain) => str_contains($this->subdomain, $domain));
|
|
|
}
|
|
|
|
|
|
- exit(400);
|
|
|
+ if (empty($matched)) {
|
|
|
+ throw new RuntimeException("[DNSPod – DescribeDomainList] The subdomain {$this->subdomain} does not match any domain in your account.");
|
|
|
+ }
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'sub' => rtrim(substr($this->subdomain, 0, -strlen($matched)), '.'),
|
|
|
+ 'domain' => $matched,
|
|
|
+ ];
|
|
|
}
|
|
|
|
|
|
- private function send(string $action, array $parameters = []): array
|
|
|
+ private function sendRequest(string $action, array $parameters = []): array
|
|
|
{
|
|
|
- $response = Http::timeout(15)->asForm()->post(self::API_HOST.$action, array_merge(['login_token' => $this->loginToken, 'format' => 'json'], $parameters));
|
|
|
-
|
|
|
- if ($response->ok()) {
|
|
|
- $data = $response->json();
|
|
|
- if (Arr::get($data, 'status.code') === 1) {
|
|
|
- return Arr::except($data, ['status']);
|
|
|
+ $timestamp = time();
|
|
|
+ $response = Http::timeout(15)->withHeaders([
|
|
|
+ 'X-TC-Action' => $action,
|
|
|
+ 'X-TC-Timestamp' => $timestamp,
|
|
|
+ 'X-TC-Version' => '2021-03-23',
|
|
|
+ 'Authorization' => $this->generateSignature($parameters, $timestamp),
|
|
|
+ 'Host' => 'dnspod.tencentcloudapi.com',
|
|
|
+ ])->withBody(json_encode($parameters, JSON_FORCE_OBJECT))->post(self::API_ENDPOINT);
|
|
|
+
|
|
|
+ $data = $response->json();
|
|
|
+ if ($data) {
|
|
|
+ $data = $data['Response'];
|
|
|
+ if ($response->ok()) {
|
|
|
+ return $data;
|
|
|
}
|
|
|
|
|
|
- Log::error('[DNSPod - '.$action.'] 返回错误信息:'.Arr::get($data, 'status.message'));
|
|
|
+ Log::error('[DNSPod – '.$action.'] 返回错误信息:'.$data['Error']['Message'] ?? 'Unknown error');
|
|
|
} else {
|
|
|
- Log::error('[DNSPod - '.$action.'] 请求失败');
|
|
|
+ Log::error('[DNSPod – '.$action.'] 请求失败');
|
|
|
}
|
|
|
|
|
|
exit(400);
|
|
|
}
|
|
|
|
|
|
+ private function generateSignature(array $parameters, int $timestamp): string
|
|
|
+ { // 签名
|
|
|
+ $date = gmdate('Y-m-d', $timestamp);
|
|
|
+ $canonicalRequest = "POST\n/\n\ncontent-type:application/json\nhost:dnspod.tencentcloudapi.com\n\ncontent-type;host\n".hash('sha256', json_encode($parameters, JSON_FORCE_OBJECT));
|
|
|
+
|
|
|
+ $credentialScope = "$date/dnspod/tc3_request";
|
|
|
+ $stringToSign = "TC3-HMAC-SHA256\n$timestamp\n$credentialScope\n".hash('sha256', $canonicalRequest);
|
|
|
+
|
|
|
+ $secretDate = hash_hmac('SHA256', $date, 'TC3'.$this->secretKey, true);
|
|
|
+ $secretService = hash_hmac('SHA256', 'dnspod', $secretDate, true);
|
|
|
+ $secretSigning = hash_hmac('SHA256', 'tc3_request', $secretService, true);
|
|
|
+ $signature = hash_hmac('SHA256', $stringToSign, $secretSigning);
|
|
|
+
|
|
|
+ return 'TC3-HMAC-SHA256 Credential='.$this->secretId.'/'.$credentialScope.', SignedHeaders=content-type;host, Signature='.$signature;
|
|
|
+ }
|
|
|
+
|
|
|
public function store(string $ip, string $type): bool
|
|
|
{
|
|
|
- $ret = $this->send('Record.Create', [
|
|
|
- 'domain' => $this->domainData['host'],
|
|
|
- 'sub_domain' => $this->domainData['rr'],
|
|
|
- 'record_type' => $type,
|
|
|
- 'record_line_id' => 0,
|
|
|
- 'value' => $ip,
|
|
|
+ return (bool) $this->sendRequest('CreateRecord', [
|
|
|
+ 'Domain' => $this->domainInfo['domain'],
|
|
|
+ 'SubDomain' => $this->domainInfo['sub'],
|
|
|
+ 'RecordType' => $type,
|
|
|
+ 'RecordLine' => '默认',
|
|
|
+ 'Value' => $ip,
|
|
|
]);
|
|
|
-
|
|
|
- return (bool) $ret;
|
|
|
}
|
|
|
|
|
|
public function update(string $latest_ip, string $original_ip, string $type): bool
|
|
|
{
|
|
|
- $record = Arr::first($this->getRecordId($type, $original_ip));
|
|
|
- if ($record) {
|
|
|
- $ret = $this->send('Record.Modify', [
|
|
|
- 'domain' => $this->domainData['host'],
|
|
|
- 'record_id' => $record,
|
|
|
- 'sub_domain' => $this->domainData['rr'],
|
|
|
- 'record_type' => $type,
|
|
|
- 'record_line_id' => 0,
|
|
|
- 'value' => $latest_ip,
|
|
|
+ $recordIds = $this->getRecordIds($type, $original_ip);
|
|
|
+ if ($recordIds) {
|
|
|
+ $result = $this->sendRequest('ModifyRecord', [
|
|
|
+ 'Domain' => $this->domainInfo['domain'],
|
|
|
+ 'RecordType' => $type,
|
|
|
+ 'RecordLine' => '默认',
|
|
|
+ 'Value' => $latest_ip,
|
|
|
+ 'RecordId' => $recordIds[0],
|
|
|
+ 'SubDomain' => $this->domainInfo['sub'],
|
|
|
]);
|
|
|
}
|
|
|
|
|
|
- return (bool) ($ret ?? false);
|
|
|
+ return (bool) ($result ?? false);
|
|
|
}
|
|
|
|
|
|
- private function getRecordId(string $type, string $ip): array|false
|
|
|
+ private function getRecordIds(string $type, string $ip): array
|
|
|
{
|
|
|
- $parameters = ['domain' => $this->domainData['host'], 'sub_domain' => $this->domainData['rr']];
|
|
|
+ $parameters = ['Domain' => $this->domainInfo['domain'], 'Subdomain' => $this->domainInfo['sub']];
|
|
|
if ($type) {
|
|
|
- $parameters['record_type'] = $type;
|
|
|
+ $parameters['RecordType'] = $type;
|
|
|
}
|
|
|
- $records = $this->send('Record.List', $parameters);
|
|
|
+ $response = $this->sendRequest('DescribeRecordList', $parameters);
|
|
|
+
|
|
|
+ if (isset($response['RecordList'])) {
|
|
|
+ $records = $response['RecordList'];
|
|
|
|
|
|
- if ($records) {
|
|
|
- $filtered = Arr::get($records, 'records');
|
|
|
if ($ip) {
|
|
|
- $filtered = Arr::where($filtered, static function (array $value) use ($ip) {
|
|
|
- return $value['value'] === $ip;
|
|
|
+ $records = array_filter($records, static function ($record) use ($ip) {
|
|
|
+ return $record['Value'] === $ip;
|
|
|
});
|
|
|
}
|
|
|
|
|
|
- return data_get($filtered, '*.id');
|
|
|
+ return array_column($records, 'RecordId');
|
|
|
}
|
|
|
|
|
|
- return false;
|
|
|
+ return [];
|
|
|
}
|
|
|
|
|
|
public function destroy(string $type, string $ip): int
|
|
|
{
|
|
|
- $records = $this->getRecordId($type, $ip);
|
|
|
- $count = 0;
|
|
|
- if ($records) {
|
|
|
- foreach ($records as $record) {
|
|
|
- $result = $this->send('Record.Remove', ['domain' => $this->domainData['host'], 'record_id' => $record]);
|
|
|
- if ($result === []) {
|
|
|
- $count++;
|
|
|
- }
|
|
|
+ $recordIds = $this->getRecordIds($type, $ip);
|
|
|
+ $deletedCount = 0;
|
|
|
+
|
|
|
+ foreach ($recordIds as $recordId) {
|
|
|
+ if ($this->sendRequest('DeleteRecord', ['Domain' => $this->domainInfo['domain'], 'RecordId' => $recordId])) {
|
|
|
+ $deletedCount++;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- return $count;
|
|
|
+ return $deletedCount;
|
|
|
}
|
|
|
}
|