阿里云DDNS PHP5封装类

/**
* 阿里云DDNS PHP5封装类
* 官方SDK过于臃肿庞大,利用accessKeyId和accessKeySecret获取API;
* 自动获取域名所有列表RecordId;
* 支持手动提交IPv6和IPv4地址;
* 默认自动获取支持IPv6和IPv4;
* 保存IPv6和IPv4地址,有变化才提交更新;
* 获取IP地址不可用时, 支持轮询;
*/

<?php

date_default_timezone_set('Asia/Shanghai');
@header("Content-type: application/json;charset=UTF-8");

function ipUrl(){
	$ipList = array(
		'IPv4' => array(
			array('ip-api.io/json'            => 180),
			array('myip.ipip.net/json'        => 30),  
			
			array('ipv4.ip159.com/getip'      => 49),
			array('ipv4.icanhazip.com'        => 53),
			array('v4.myip.la'                => 170),
			array('api-ipv4.ip.sb/geoip'      => 200),
			array('ipv4-overseas.itdog.plus'  => 166),
			array('ipv4_ct.itdog.cn'          => 12),
			array('ipv4_cu.itdog.cn'          => 11),
			array('ipv4_cm.itdog.cn'          => 11),
			array('4.ipshudi.com'             => 33),
			array('4.ipw.cn/api/ip/myip?json' => 33),
			
			
		),
		'IPv6' => array(
			array('ipv6.ip159.com/getip'      => 76),
			array('ipv6.icanhazip.com'        => 170),
			array('v6.myip.la'                => 202),
			array('api-ipv6.ip.sb/geoip'      => 140),
			array('ipv6-overseas.itdog.plus'  => 178),
			array('ipv6_ct.itdog.cn'          => 17),
			array('ipv6_cu.itdog.cn'          => 20),
			array('ipv6_cm.itdog.cn'          => 20),
			array('6.ipshudi.com'             => 30),
			array('6.ipw.cn/api/ip/myip?json' => 33),
		)
	);
	$result = [
        'IPv4' => null,
        'IPv6' => null
    ];

    // 处理IPv4
    $minIPv4 = PHP_INT_MAX;
    foreach($ipList['IPv4'] as $ipArray){
        foreach($ipArray as $url => $time){
            if($time < $minIPv4) {
                $minIPv4 = $time;
                $result['IPv4'] = $url;
            }
        }
    }

    // 处理IPv6
    $minIPv6 = PHP_INT_MAX;
    foreach($ipList['IPv6'] as $ipArray){
        foreach($ipArray as $url => $time){
            if($time < $minIPv6){
                $minIPv6 = $time;
                $result['IPv6'] = $url;
            }
        }
    }

    return $result;
}

/**
 * 阿里云DDNS PHP5封装类
 * 官方SDK过于臃肿庞大,利用accessKeyId和accessKeySecret获取API; 
 * 自动获取域名所有列表RecordId; 
 * 支持手动提交IPv6和IPv4地址; 
 * 默认自动获取支持IPv6和IPv4;
 * 保存IPv6和IPv4地址,有变化才提交更新; 
 * 获取IP地址不可用时, 支持轮询; 
 */
class AliyunDDNS{
    private $endpoint        = 'https://alidns.aliyuncs.com';
    private $version         = '2015-01-09';
    private $accessKeyId     = 'LT********************4j';
    private $accessKeySecret = 'S2**************************D5';
	private $domain          = 'xxxxxx.com';
	private $rr              = '*';
	private $ipv4;
	private $ipv6;
	private $ipv4Type        = 'A';
	private $ipv6Type        = 'AAAA';

    /**
     * 构造函数
     * @param string $accessKeyId     阿里云AccessKey ID
     * @param string $accessKeySecret 阿里云AccessKey Secret
     */
    public function __construct($accessKeyId = null, $accessKeySecret = null, $domain = null, $rr = null){
        $this->accessKeyId     = $accessKeyId ? $accessKeyId : $this->accessKeyId;
        $this->accessKeySecret = $accessKeySecret ? $accessKeySecret : $this->accessKeySecret;
		$this->domain          = $domain ? $domain : $this->domain;
		$this->rr              = $rr ? $rr : $this->rr;
    }
	// 手动设置IPv4, 将不再自动获取IPv4;未设置,将从网络自动获取
    public function setIpv4($str){
		$this->ipv4 = $str;
		return ;
	}
	
	// 手动设置IPv6, 将不再自动获取IPv4;未设置,将从网络自动获取
	public function setIpv6($str){
		$this->ipv6 = $str;
		return ;
	}
	
	// 设置 如:@、A、CNAME、MX、NS、TXT、SRV、SOA、PTR
	public function setIpv4Type($str){
		$this->ipv4Type = $str;
		return ;
	}
	// 设置 如:@、AAAA、CNAME、MX、NS、TXT、SRV、SOA、PTR
	public function setIpv6Type($str){
		$this->ipv6Type = $str;
		return ;
	}
	
	// 阿里云AccessKey,报错,注意给权限
	public function setAccessKeyId($str){
		$this->accessKeyId = $str;
		return ;
	}
	
	// 阿里云AccessKeySecret
	public function setAccessKeySecret($str){
		$this->accessKeySecret = $str;
		return ;
	}
	
	// 设置域名, 如:www.zhouein.com中的zhouein.com
	public function setDomain($str){
		$this->domain = $str;
		return ;
	}
	
	// 设置二级域名, 如:api.zhouein.com中的'api'或'www'或'*'或'@'
	public function setRr($str){
		$this->rr = $str;
		return ;
	}
	
    // 循环得到有效的IP地址, 当URL不可用时,  跳到下一个.
	private function getLoopRes($domains, $ipType){
		foreach($domains[$ipType] as $domain){
			try {
				$res = $this->cURL($domain);
				
				if(is_array($res)){
					$ip = $res['ip'];
					//判断获取到的IP是IPv4或IPv6
					$checkFunc = ($ipType === 'ipv4') ? 'isPublicIPv4' : 'isPublicIPv6'; 
					if($this->{$checkFunc}($ip)){
						return ['domain' => $domain, 'ip' => $ip];
					}
				}
				
			} catch (Exception $e){
				continue; // 发生异常时尝试下一个域名
			}
		}
		return false; // 所有域名都失败时返回false
	}

	private function getIP(){
		$ips = array(
			'ipv4' => array(
				'ipv4_ct.itdog.cn',
				'ipv4_cu.itdog.cn',
				'ipv4_cm.itdog.cn'
			),
			'ipv6' => array(
				'ipv6_ct.itdog.cn',
				'ipv6_cu.itdog.cn',
				'ipv6_cm.itdog.cn'
			)
		);

		$result = [
			'ipv4' => $this->getLoopRes($ips, 'ipv4'),
			'ipv6' => $this->getLoopRes($ips, 'ipv6')
		];

		return $result;
	}

	// 是否指定IP地址, 没有则从列表中获取, 如果服务器中保存的IP地址未变化, 则不更新
    private function getCurrentIp($type){
		$publicipFile = 'json/' . $this->domain . '.json';
        
		if(!$this->$type){
			$this->$type = $this->getIP()[$type]['ip'];
		}
		
        if (!$this->$type || !$this->validateIp($type, $this->$type)) {
            return false;
        }

        $last_update = date('Y-m-d H:i:s');
        // 检查目录是否存在,不存在则创建
        if (!is_dir('json')) {
            mkdir('json', 0755, true);
        }

        $newIPArr = [
            'ipv4' => ['ip' => null, 'last_update' => $last_update],
            'ipv6' => ['ip' => null, 'last_update' => $last_update]
        ];
        $newIPArr[$type]['ip'] = $this->$type;

        // 检查文件是否存在
        if (!file_exists($publicipFile)) {
            file_put_contents($publicipFile, json_encode($newIPArr));
            return ['type' => $type, 'ip' => $this->$type];
        }

        // 读取现有IP数据
        $saved_ips = json_decode(file_get_contents($publicipFile), true);

        // 比较IP是否有变化
        if ($saved_ips[$type]['ip'] === $this->$type) {
            return false;
        }

        $saved_ips[$type]['ip'] = $this->$type;
        $saved_ips[$type]['last_update'] = $last_update;
        file_put_contents($publicipFile, json_encode($saved_ips));

        return ['type' => $type, 'ip' => $this->$type];
    }
	
	// 验证IPv6地址是否有效
	private function isPublicIPv6($ip){
		// 排除私有地址(fc00::/7)
		$ipBin = inet_pton($ip);
		if(($ipBin[0] == "\xfc") || ($ipBin[0] == "\xfd") || 
		   ($ipBin[0] == "\xfe" && ($ipBin[1] & "\xc0") == "\x80")) {
			return false;
		}
		$result = filter_var($ip, FILTER_VALIDATE_IP, 
			FILTER_FLAG_IPV6 | 
			FILTER_FLAG_NO_PRIV_RANGE | 
			FILTER_FLAG_NO_RES_RANGE
		);
		return $result; // 返回标准化后的IPv6地址
	}
	
	// 验证IPv4地址是否有效
	private function isPublicIPv4($ip){
		// 排除多播地址(224.0.0.0/4)
		$octets = explode('.', $ip);
		if(isset($octets[0]) && $octets[0] >= 224 && $octets[0] <= 239) {
			return false;
		}
		$result = filter_var($ip, FILTER_VALIDATE_IP, 
			FILTER_FLAG_IPV4 | 
			FILTER_FLAG_NO_PRIV_RANGE | 
			FILTER_FLAG_NO_RES_RANGE
		);
		return $result; // 返回标准化后的IPv4地址
	}
	
    private function validateIp($type, $ip) {
        return $type === 'ipv4' ? $this->isPublicIPv4($ip) : $this->isPublicIPv6($ip);
    }

    // 生成签名
	private function generateSignature($params){
		ksort($params);
		$canonicalizedQuery = '';
		foreach ($params as $key => $value) {
			$canonicalizedQuery .= '&' . rawurlencode($key) . '=' . rawurlencode($value);
		}
		$stringToSign = 'GET&%2F&' . rawurlencode(substr($canonicalizedQuery, 1));
		return base64_encode(hash_hmac('sha1', $stringToSign, $this->accessKeySecret . '&', true));
	}
	
	// 封装cURL 网络请求GET方法
	private function cURL($url){
		$ch = curl_init();
		curl_setopt($ch, CURLOPT_URL, $url);
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
		$response = curl_exec($ch);
		if(curl_errno($ch)){
            throw new Exception(curl_error($ch));
        }
		curl_close($ch);
		$result = json_decode($response, true);
		return $result; 
	}
	
	// 请求阿里云DDNS公共参数
	private function publicParams($params){
		$publicParams = array(
            'Format'           => 'JSON',
            'Version'          => $this->version,
            'AccessKeyId'      => $this->accessKeyId,
            'SignatureMethod'  => 'HMAC-SHA1',
            'Timestamp'        => gmdate('Y-m-d\TH:i:s\Z'),
            'SignatureVersion' => '1.0',
            'SignatureNonce'   => md5(uniqid(mt_rand(), true))
        );
		return array_merge($publicParams, $params);
	}
	
	// 获取解析记录ID
	private function getRecordId(){
		$arr = array(
			'Action'     => 'DescribeDomainRecords',
			'DomainName' => $this->domain,
		);
		$params = $this->publicParams($arr);
		$params['Signature'] = $this->generateSignature($params);
		$url = 'http://alidns.aliyuncs.com/?' . http_build_query($params);
		$response = $this->cURL($url);
		$yumingList = $response['DomainRecords']['Record'];
		$result = array();
		foreach($yumingList as $key => $val){
			if($val['RR'] == $this->rr){
				$result[] = $val;
			}
		}
		return $result;
    }

    // 更新域名解析记录
	private function updateDNS($RecordId,$Type,$newIP){
		$arr = array(
			'Action'           => 'UpdateDomainRecord',  //AddDomainRecord
			'RecordId'         => $RecordId,
			'RR'               => $this->rr,
			'Type'             => $Type,
			'Value'            => $newIP,
		);
		$params = $this->publicParams($arr);
		$params['Signature'] = $this->generateSignature($params);
		$url = 'http://alidns.aliyuncs.com/?' . http_build_query($params);
		$response = $this->cURL($url);
		
		return $response;
    }
	
	public function main(){
		$result = [];
		$getIpv4 = $this->getCurrentIp('ipv4');
        $getIpv6 = $this->getCurrentIp('ipv6');
		if(!$getIpv4 && !$getIpv6) {
			return [
				'code' => 200,
				'msg' => '未更新',
				'data' => [
					'ipv4' => $this->ipv4,
					'ipv6' => $this->ipv6
				]
			];
		}

		foreach($this->getRecordId() as $key => $record){
			$result[$key] = $record;
			
			if($record['Type'] === 'A' && !empty($getIpv4['ip'])) {
				if(filter_var($getIpv4['ip'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)){
					$result[$key]['req'] = $this->ipv4;
					$result[$key]['res'] = $this->updateDNS(
						$record['RecordId'],
						$record['Type'],
						$getIpv4['ip']
					);
				}
			}
			
			if($record['Type'] === 'AAAA' && !empty($getIpv6['ip'])){
				if(filter_var($getIpv6['ip'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)){
					$result[$key]['req'] = $this->ipv6;
					$result[$key]['res'] = $this->updateDNS(
						$record['RecordId'],
						$record['Type'],
						$getIpv6['ip']
					);
				}
			}
		}
		
		return $result;
	}
	
	private function logError($message) {
        error_log("[DNS Helper] ".date('Y-m-d H:i:s')." - {$message}\n", 3, 'dns_error.log');
    }
	
	// 废弃
    private function percentEncode($str) {
        $res = urlencode($str);
        $res = str_replace(array('+', '*'), array('%20', '%2A'), $res);
        $res = preg_replace('/%7E/', '~', $res);
        return $res;
    }

}

// 使用示例
$ddns = new AliyunDDNS(
	$accessKeyId     = 'LT********************4j',
	$accessKeySecret = 'S2**************************D5',
	$domain          = 'xxxxxx.com',  // 你的主域名
	$rr              = '*'             // 子域名
 );
//$ddns->setIpv4('111.111.111.111');  // 固定公网IPv4地址
//$ddns->setIpv6('2400:3200::1');     // 固定公网IPv6地址

//$ddns->setIpv4Type('@');            // 如:@、AAAA、CNAME、MX、NS、TXT、SRV、SOA、PTR
//$result = $ddns->updateDomainRecord($domain, $rr);
$result = $ddns->main();
echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
?>

  

posted @ 2025-07-13 13:23  钢锅  阅读(11)  评论(0)    收藏  举报