阿里云域名云解析服务(DNS)API接口PHP封装
目前互联网上提供免费的域名托管服务的厂家很多,如阿里云/万网、腾讯云、CloudFlare、华为云等等,我们将域名托管到这些云解析DNS服务商后,虽然服务商都提供了控制台用于域名及解析记录的管理,但是有时候如果我们需要自己实现DDNS动态域名解析等一些基于官网API接口能力的功能,就需要使用到官网提供的API能力,本文将针对阿里云/万网的API接口,通过PHP封装了常用的函数类,用于托管域名、记录集等的管理。
本文旨在帮助开发者快速使用集成华为云云解析服务(DNS)的API接口,实现对域名(Zone)和解析记录(Record Set)的自动化管理,如域名托管、记录添加及修改等。
下面是PHP实现的API封装类,代码如下:
/**
* 阿里云域名服务、域名DNS解析管理实现类文件
* 主要实现
* 1、阿里云账号下的注册域名清单查询、域名信息查询、DS配置查询、NS查询及修改
* 2、阿里云账号下的DNS解析的域名的A、AAA、CNAME等记录的增删改查等操作
* DNS文档地址:https://api.aliyun.com/api-tools/demo/Alidns/87dd2c5a-bb87-4269-8bb1-6e021be52123
* 域名文档地址:https://api.aliyun.com/document/Domain/2018-01-29/overview
* 基于自定义封装API调用接口协议
* 调用方法:AliDomainDNS::Obj()->QueryDomainList($Param);
* 引用或者使用本文,请注明来源:楚狐在线- https://chuhu.org
**/
class AliDomainDNS
{
private $ALGORITHM; //加密算法
private $AccessKeyId; //Access Key ID
private $AccessKeySecret; //Access Key Secret
private static $obj = null;
public function __construct()
{
date_default_timezone_set('UTC'); // 设置时区为GMT
$this->AccessKeyId = "*************"; // 设置RAM用户Access Key ID
$this->AccessKeySecret = "****************"; //设置RAM用户Access Key Secret
$this->ALGORITHM = 'ACS3-HMAC-SHA256'; // 设置加密算法
}
public static function Obj ()
{
if(is_null(self::$obj))
{
self::$obj = new self();
}
return self::$obj;
}
//***********************以下为域名DNS管理服务***********************//
//根据传入域名获取域名当前的NS服务器列表,以及NS是否属于阿里云解析管理
public function DescribeDomainNs($domainName)
{
$request = $this->createRequest('DescribeDomainNs');
$request['queryParam'] = [
"DomainName" => $domainName
];
$this->getAuthorization($request);
$rtn = $this->callApi($request);
return $rtn;
}
//变更域名绑定的DNS服务器名称,从其他服务商的DNS服务器名称,修改为阿里云解析DNS提供的DNS服务器名称
//设置$domainName的DNS服务器为阿里云默认的DNS服务器。
public function ModifyHichinaDomainDNS($domainName)
{
$request = $this->createRequest('ModifyHichinaDomainDNS');
$request['queryParam'] = [
"DomainName" => $domainName
];
$this->getAuthorization($request);
$rtn = $this->callApi($request);
return $rtn;
}
//获取域名列表
public function DescribeDomains()
{
$request = $this->createRequest('DescribeDomains');
$request['queryParam'] = [
"PageSize" => 100
];
$this->getAuthorization($request);
$rtn = $this->callApi($request);
return $rtn;
}
//增加域名解析记录:增加单域名记录值
public function AddDomainRecord($domainName, $RR, $recordType, $value)
{
$request = $this->createRequest('AddDomainRecord');
$request['queryParam'] = [
"DomainName" => $domainName,
"RR" => $RR,
"Type" => $recordType,
"Value" => $value,
"TTL" => 600,
"Line" => "default"
];
$this->getAuthorization($request);
$rtn = $this->callApi($request);
//return $rtn;
echo printJsonToHTML($rtn);
}
//增加域名解析记录:增加单域名记录值
public function AddDomainRecord2($domainName, $RR, $recordType, $value)
{
$request = $this->createRequest('AddDomainRecord');
$request['queryParam'] = [
"DomainName" => $domainName,
"RR" => $RR,
"Type" => $recordType,
"Value" => $value,
"TTL" => 600,
"Line" => "default"
];
$this->getAuthorization($request);
$rtn = $this->callApi($request);
return $rtn;
}
//DDNS更新处理,若域名记录不存在,则直接创建 (DDNS主用)
//$Domain 主域名
//$Domain 耳机域名名称,不需要完整的。
//主要用于DDNS的更新
//与UpdateDNSRecord传入参数一样,增加了记录值是否存在的判断。
public function ModifyDynamicDNS($RR,$domainName, $value, $recordType="A")
{
//查出主域名下记录,看是否子域名的解析记录。
$data = $this->DescribeDomainRecords2($domainName);
$data = json_decode($data,true);
$data = array_filter($data['DomainRecords']['Record'], function($item) use ($RR, $recordType) {
return $item['RR'] === $RR && $item['Type'] === $recordType;
});
$data = array_values($data); //重构数组,删除空
$rc = count($data);
if($rc==0)
{
//没有记录, 创建按记录。
$rtn = $this->AddDomainRecord2($domainName, $RR, $recordType, $value);
$rtn = json_decode($rtn,true);
if(count(array_column($rtn,'Error'))==1){
echo "域名".$RR.".".$domainName."的".$recordType."记录不存在,创建失败。";
}else {
echo "域名".$RR.".".$domainName."创建成功,并配置".$recordType."记录为:".$value;
}
}
else if($rc>1)
{
echo "域名".$RR.".".$domainName."的".$recordType."记录有".$rc."条,更新记录失败!";
} else {
if($data[0]['Value']==$value){
echo "处理完成:".$RR.".".$domainName."当前".$data[0]['Type']."记录【".$data[0]['Value']."】与新值【".$value."】一致,无需修改。";
} else {
$request = $this->createRequest('UpdateDomainRecord');
$request['queryParam'] = [
"RecordId" => $data[0]['RecordId'],
"RR" => $RR,
"Type" => $recordType,
"Value" => $value
];
$this->getAuthorization($request);
$val = $this->callApi($request);
$val = json_decode($val,true);
if(isset($val['Message']))
{
echo "处理失败:".$val['Message'];
}
else
{
if($RR=="@")
{
echo "处理完成:".$domainName."的".$recordType."记录【".$data[0]['Value']."】已修改为【".$value."】。";
}
else
{
echo "处理完成:".$RR.".".$domainName."的".$recordType."记录【".$data[0]['Value']."】已修改为【".$value."】。";
}
}
}
}
}
//更新域名解析记录
public function UpdateDomainRecord($recordId, $RR, $recordType, $value)
{
$request = $this->createRequest('UpdateDomainRecord');
$request['queryParam'] = [
"RecordId" => $recordId,
"RR" => $RR,
"Type" => $recordType,
"Value" => $value
];
$this->getAuthorization($request);
$rtn = $this->callApi($request);
//return $rtn;
echo printJsonToHTML($rtn);
}
//更新域名解析记录
public function UpdateDomainRecord2($recordId, $RR, $recordType, $value)
{
$request = $this->createRequest('UpdateDomainRecord');
$request['queryParam'] = [
"RecordId" => $recordId,
"RR" => $RR,
"Type" => $recordType,
"Value" => $value
];
$this->getAuthorization($request);
$rtn = $this->callApi($request);
return $rtn;
}
//删除域名解析记录:删除单域名记录值,目前只能一个记录一个记录的删除。
public function DeleteDomainRecord($recordId)
{
$request = $this->createRequest('DeleteDomainRecord');
$request['queryParam'] = [
"RecordId" => $recordId
];
$this->getAuthorization($request);
$rtn = $this->callApi($request);
//return $rtn;
echo printJsonToHTML($rtn);
}
//删除域名解析记录:删除单域名记录值,目前只能一个记录一个记录的删除。
public function DeleteDomainRecord2($recordId)
{
$request = $this->createRequest('DeleteDomainRecord');
$request['queryParam'] = [
"RecordId" => $recordId
];
$this->getAuthorization($request);
$rtn = $this->callApi($request);
return $rtn;
//echo printJsonToHTML($rtn);
}
//根据传入参数删除主机记录对应的解析记录
//recordType 可以为null
public function DeleteSubDomainRecords($MyDomainName, $RR, $recordType=null)
{
$request = $this->createRequest('DeleteSubDomainRecords');
$request['queryParam'] = [
"DomainName" => $MyDomainName,
"RR" => $RR
];
if($recordType!=null)
{
$request['queryParam']['Type'] = $recordType;
}
$this->getAuthorization($request);
$rtn = $this->callApi($request);
//return $rtn;
echo printJsonToHTML($rtn);
}
//根据传入参数删除主机记录对应的解析记录
//recordType 可以为null
public function DeleteSubDomainRecords2($MyDomainName, $RR, $recordType=null)
{
$request = $this->createRequest('DeleteSubDomainRecords');
$request['queryParam'] = [
"DomainName" => $MyDomainName,
"RR" => $RR
];
if($recordType!=null)
{
$request['queryParam']['Type'] = $recordType;
}
$this->getAuthorization($request);
$rtn = $this->callApi($request);
return $rtn;
//echo printJsonToHTML($rtn);
}
//删除单个域名下的所有解析记录ali
function DeleteRecordList($MyDomainName)
{
//查询源域名记录
$data = $this->DescribeDomainRecords2($MyDomainName);
$data = json_decode($data,true);
//print_r($data);
$ListCount = $data['TotalCount'];
if($ListCount==0)
{
echo "域名【".$MyDomainName."】当前主机接西记录列表为空,无需删除!
\n";
return;
}
echo "下面将删除域名【".$MyDomainName."】的".$ListCount."条记录:
\n";
foreach($data['DomainRecords']['Record'] as $v)
{
$rtn = $this->DeleteDomainRecord2($v['RecordId']);
$rtn = json_decode($rtn,true);
//print_r($rtn);
if(isset($rtn['Message']))
{
echo "域名 ".$MyDomainName." 的主机 ".$v['RR']." 的 ".$v['Type']." 记录[ ".$v['Value']." ]删除失败:".$rtn['Message']."(Code:".$rtn['Code'].")
\n";
}
else
{
echo "域名 ".$MyDomainName." 的主机 ".$v['RR']." 的 ".$v['Type']." 记录[ ".$v['Value']." ]删除成功(RecordId=".$v['RecordId'].")。
\n";
}
}
}
//根据传入参数获取指定主域名的所有解析记录列表
public function DescribeDomainRecords($MyDomainName)
{
$request = $this->createRequest('DescribeDomainRecords');
$request['queryParam'] = [
"PageSize" => 200,
//"OrderBy" => "RR",
"DomainName" => $MyDomainName
];
$this->getAuthorization($request);
$rtn = $this->callApi($request);
//return $rtn;
echo printJsonToHTML($rtn);
}
//根据recordId获取解析记录的详细信息
public function DescribeDomainRecordInfo($recordId)
{
$request = $this->createRequest('DescribeDomainRecordInfo');
$request['queryParam'] = [
"RecordId" => $recordId
];
$this->getAuthorization($request);
$rtn = $this->callApi($request);
//return $rtn;
echo printJsonToHTML($rtn);
}
//根据recordId获取解析记录的详细信息
public function DescribeDomainRecordInfo2($recordId)
{
$request = $this->createRequest('DescribeDomainRecordInfo');
$request['queryParam'] = [
"RecordId" => $recordId
];
$this->getAuthorization($request);
$rtn = $this->callApi($request);
return $rtn;
//echo printJsonToHTML($rtn);
}
//根据recordId获取解析记录的IP解析记录
public function DescribeDomainRecordValue($recordId)
{
$request = $this->createRequest('DescribeDomainRecordInfo');
$request['queryParam'] = [
"RecordId" => $recordId
];
$this->getAuthorization($request);
$val = $this->callApi($request);
$arr = (array) json_decode($val,true);
return $arr['Value'];
}
//根据传入参数获取某个固定子域名的所有解析记录列表
//查询某个二级域名的所有记录或者某个类型的记录
//recordType 可以为 null
//$SubDomain 为完整的二级域名 如test.xxxx.com
public function DescribeSubDomainRecords($SubDomain, $recordType=null)
{
$request = $this->createRequest('DescribeSubDomainRecords');
$request['queryParam'] = [
"SubDomain" => $SubDomain
];
if($recordType!=null)
{
$request['queryParam']['Type'] = $recordType;
}
$this->getAuthorization($request);
$rtn = $this->callApi($request);
//return $rtn;
echo printJsonToHTML($rtn);
}
//根据传入参数获取某个固定子域名的所有解析记录列表
//查询某个二级域名的所有记录或者某个类型的记录
//recordType 可以为 null
//$SubDomain 为完整的二级域名 如test.example.com
public function DescribeSubDomainRecords2($SubDomain, $recordType=null)
{
$request = $this->createRequest('DescribeSubDomainRecords');
$request['queryParam'] = [
"SubDomain" => $SubDomain
];
if($recordType!=null)
{
$request['queryParam']['Type'] = $recordType;
}
$this->getAuthorization($request);
$rtn = $this->callApi($request);
return $rtn;
//echo printJsonToHTML($rtn);
}
//****************以上为域名DNS解析、下为域名管理服务******************//
//按域名查询域名信息
public function QueryDomainByDomainName($domainName)
{
//设置Action
$request = $this->createDMRequest('QueryDomainByDomainName');
//请求参数如下:
$request['queryParam'] = [
"DomainName" => $domainName
];
$this->getAuthorization($request);
$rtn = $this->callApi($request);
return $rtn;
}
//分页查询自己账户下的域名列表
public function QueryDomainList()
{
//设置Action
$request = $this->createDMRequest('QueryDomainList');
//请求参数如下:
$request['queryParam'] = [
"PageNum" => 1,
"PageSize" => 500,
"OrderByType" => 'ASC'
];
$this->getAuthorization($request);
$rtn = $this->callApi($request);
return $rtn;
}
//提交批量修改域名DNS任务
//$AliyunDns传字符型的 true ,false 如:'false'
//$domainName = ['aaaa.net'];
//$domainNameServer = array('aaaa.ns.cloudflare.com','bbbb.ns.cloudflare.com'); 当$AliyunDns='true'时,可以不设置参数。如下:
//SaveBatchTaskForModifyingDomainDns($domainName,'true') 改为默认的阿里云DNS
public function SaveBatchTaskForModifyingDomainDns($domainName, $AliyunDns, $domainNameServer=null)
{
//设置Action
$request = $this->createDMRequest('SaveBatchTaskForModifyingDomainDns');
//请求参数如下:
$request['queryParam'] = [
"Lang" => 'zh',
"AliyunDns" => $AliyunDns,
"DomainName" => $domainName,
"DomainNameServer" => $domainNameServer
];
$this->getAuthorization($request);
$rtn = $this->callApi($request);
return $rtn;
}
//查询域名DS记录
public function QueryDSRecord($domainName)
{
//设置Action
$request = $this->createDMRequest('QueryDSRecord');
//请求参数如下:
$request['queryParam'] = [
"DomainName" => $domainName
];
$this->getAuthorization($request);
$rtn = $this->callApi($request);
return $rtn;
}
//提交创建DS记录任务
public function SaveSingleTaskForAddingDSRecord($domainName,$KeyTag,$Algorithm,$DigestType,$Digest)
{
//设置Action
$request = $this->createDMRequest('SaveSingleTaskForAddingDSRecord');
//请求参数如下:
$request['queryParam'] = [
"DomainName" => $domainName,
"KeyTag" => $KeyTag,
"Algorithm" => $Algorithm,
"DigestType" => $DigestType,
"Digest" => $Digest
];
$this->getAuthorization($request);
$rtn = $this->callApi($request);
return $rtn;
}
//提交删除DS记录任务
public function SaveSingleTaskForDeletingDSRecord($domainName, $KeyTag)
{
//设置Action
$request = $this->createDMRequest('SaveSingleTaskForDeletingDSRecord');
//请求参数如下:
$request['queryParam'] = [
"DomainName" => $domainName,
"KeyTag" => $KeyTag
];
$this->getAuthorization($request);
$rtn = $this->callApi($request);
return $rtn;
}
//查询指定域名服务任务的详情列表
public function QueryTaskDetailList($TaskNo)
{
//设置Action
$request = $this->createDMRequest('QueryTaskDetailList');
//请求参数如下:
$request['queryParam'] = [
"TaskNo" => $TaskNo,
"PageNum" => 1,
"PageSize" => 1000
];
$this->getAuthorization($request);
$rtn = $this->callApi($request);
return $rtn;
}
//***********************以上为域名管理服务***********************//
//用于阿里云域名DNS解析方法调用,如域名A、AAAA、CNAME记录的增删改查
private function createRequest($xAcsAction)
{
$httpMethod = 'POST'; //请求方法
$canonicalUri = '/'; //规范化URI
$host = 'alidns.aliyuncs.com'; //默认服务地址,就近选择
//$host = 'alidns.cn-chengdu.aliyuncs.com'; //成都
//$host = 'alidns.ap-southeast-1.aliyuncs.com'; //新加坡
//$host = 'alidns.ap-northeast-2.aliyuncs.com'; //首尔
//$host = 'alidns.us-west-1.aliyuncs.com'; //硅谷 挨近 西雅图
$xAcsVersion = '2015-01-09'; //API 版本
$headers = [
'host' => $host,
'x-acs-action' => $xAcsAction,
'x-acs-version' => $xAcsVersion,
'x-acs-date' => gmdate('Y-m-d\TH:i:s\Z'),
'x-acs-signature-nonce' => bin2hex(random_bytes(16)),
];
return [
'httpMethod' => $httpMethod,
'canonicalUri' => $canonicalUri,
'host' => $host,
'headers' => $headers,
'queryParam' => [],
'body' => null,
];
}
//用于阿里云域名管理服务调用,如:修改域名NS服务器、DS记录等
private function createDMRequest($xAcsAction)
{
$httpMethod = 'POST'; //请求方法
$canonicalUri = '/'; //规范化URI
$host = 'domain.aliyuncs.com'; //默认服务地址
$xAcsVersion = '2018-01-29'; //API 版本
$headers = [
'host' => $host,
'x-acs-action' => $xAcsAction,
'x-acs-version' => $xAcsVersion,
'x-acs-date' => gmdate('Y-m-d\TH:i:s\Z'),
'x-acs-signature-nonce' => bin2hex(random_bytes(16)),
];
return [
'httpMethod' => $httpMethod,
'canonicalUri' => $canonicalUri,
'host' => $host,
'headers' => $headers,
'queryParam' => [],
'body' => null,
];
}
private function getAuthorization(&$request)
{
$request['queryParam'] = $this->processObject($request['queryParam']);
$canonicalQueryString = $this->buildCanonicalQueryString($request['queryParam']);
$hashedRequestPayload = hash('sha256', $request['body'] ?? '');
$request['headers']['x-acs-content-sha256'] = $hashedRequestPayload;
$canonicalHeaders = $this->buildCanonicalHeaders($request['headers']);
$signedHeaders = $this->buildSignedHeaders($request['headers']);
$canonicalRequest = implode("\n", [
$request['httpMethod'],
$request['canonicalUri'],
$canonicalQueryString,
$canonicalHeaders,
$signedHeaders,
$hashedRequestPayload,
]);
$hashedCanonicalRequest = hash('sha256', $canonicalRequest);
$stringToSign = "{$this->ALGORITHM}\n$hashedCanonicalRequest";
$signature = strtolower(bin2hex(hash_hmac('sha256', $stringToSign, $this->AccessKeySecret, true)));
$authorization = "{$this->ALGORITHM} Credential={$this->AccessKeyId},SignedHeaders=$signedHeaders,Signature=$signature";
$request['headers']['Authorization'] = $authorization;
}
private function callApi($request)
{
try {
// 通过cURL发送请求
$url = "https://" . $request['host'] . $request['canonicalUri'];
// 添加请求参数到URL
if (!empty($request['queryParam'])) {
$url .= '?' . http_build_query($request['queryParam']);
}
//echo $url;
// 初始化cURL会话
$ch = curl_init();
// 设置cURL选项
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 禁用SSL证书验证,请注意,这会降低安全性,不应在生产环境中使用(不推荐!!!)
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 返回而不是输出内容
curl_setopt($ch, CURLOPT_HTTPHEADER, $this->convertHeadersToArray($request['headers'])); // 添加请求头
// 根据请求类型设置cURL选项
switch ($request['httpMethod']) {
case "GET":
break;
case "POST":
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $request['body']);
break;
case "DELETE":
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE");
break;
default:
return "Unsupported HTTP method: " . $request['body'];
throw new Exception("Unsupported HTTP method");
}
// 发送请求
$result = curl_exec($ch);
// 检查是否有错误发生
if (curl_errno($ch)) {
return "Failed to send request: " . curl_error($ch);
} else {
return $result;
}
} catch (Exception $e) {
return "Error: " . $e->getMessage();
} finally {
// 关闭cURL会话
curl_close($ch);
}
}
function formDataToString($formData)
{
$res = self::processObject($formData);
return http_build_query($res);
}
function processObject($value)
{
// 如果值为空,则无需进一步处理
if ($value === null) {
return;
}
$tmp = [];
foreach ($value as $k => $v) {
if (0 !== strpos($k, '_')) {
$tmp[$k] = $v;
}
}
return self::flatten($tmp);
}
private static function flatten($items = [], $delimiter = '.', $prepend = '')
{
$flatten = [];
foreach ($items as $key => $value) {
$pos = \is_int($key) ? $key + 1 : $key;
if (\is_object($value)) {
$value = get_object_vars($value);
}
if (\is_array($value) && !empty($value)) {
$flatten = array_merge(
$flatten,
self::flatten($value, $delimiter, $prepend . $pos . $delimiter)
);
} else {
if (\is_bool($value)) {
$value = true === $value ? 'true' : 'false';
}
$flatten["$prepend$pos"] = $value;
}
}
return $flatten;
}
private function convertHeadersToArray($headers)
{
$headerArray = [];
foreach ($headers as $key => $value) {
$headerArray[] = "$key: $value";
}
return $headerArray;
}
private function buildCanonicalQueryString($queryParams)
{
ksort($queryParams);
// Build and encode query parameters
$params = [];
foreach ($queryParams as $k => $v) {
if (null === $v) {
continue;
}
$str = rawurlencode($k);
if ('' !== $v && null !== $v) {
$str .= '=' . rawurlencode($v);
} else {
$str .= '=';
}
$params[] = $str;
}
return implode('&', $params);
}
private function buildCanonicalHeaders($headers)
{
// Sort headers by key and concatenate them
uksort($headers, 'strcasecmp');
$canonicalHeaders = '';
foreach ($headers as $key => $value) {
$canonicalHeaders .= strtolower($key) . ':' . trim($value) . "\n";
}
return $canonicalHeaders;
}
private function buildSignedHeaders($headers)
{
// Build the signed headers string
$signedHeaders = array_keys($headers);
sort($signedHeaders, SORT_STRING | SORT_FLAG_CASE);
return implode(';', array_map('strtolower', $signedHeaders));
}
}
下面提供一个简单的API调用示例,如下:
# 自己账户下的域名列表
$rtn = AliDomainDNS::Obj()->QueryDomainList()
print_r($rtn);
# 获取指定主域名的所有解析记录列表
$rtn = AliDomainDNS::Obj()->DescribeDomainRecords("mydomain.com");
print_r($rtn);
通过这个PHP封装类,我就可以用自己的域名,在自己的家庭网络设备如NAS上实现家里宽带的动态域名解析了,您可以创建一个ddns.php文件,核心代码如下:
//引入前面的类文件
require_once($_SERVER['DOCUMENT_ROOT'].'/AliDomainDNS-inc.php');
#获取当前客户端的ip
$REMOTE_ADDR = $_SERVER['REMOTE_ADDR'];
//IPv4地址更新
if(filter_var($REMOTE_ADDR, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4))
{
AliDomainDNS::Obj()->ModifyDynamicDNS("ddns","example.com",$REMOTE_ADDR,"A");
}
//IPv6地址更新
if(filter_var($REMOTE_ADDR, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6))
{
AliDomainDNS::Obj()->ModifyDynamicDNS("ddns","example.com",$REMOTE_ADDR,"AAAA");
}
|