用Delphi调用阿里云的OpenAPI更新动态域名解析记录

用Delphi调用阿里云的OpenAPI更新动态域名解析记录

       家里一直是电信的宽带,虽然只是200M下行30M上行的平均水平,但是对于平时下载量不大的我来说已经绰绰有余了,很多时候需要从外网访问家里的NAS的资料,但是因为不是固定IP,每次一重启路由器或者每隔几天,家里的IP地址都是变动一下,前阵子一直使用docker安装了一个阿里云的DDNS软件非常好用,突然有一天不更新IP到阿里云的服务器上了,又加上自己的管理软件的服务端是安装到Windows下(用unRaidNAS虚拟了一个WindowsServer),所以干脆想办法把这个功能加到自己的管理服务端软件上,这时候开始祭出我们的工具Delphi。(前几篇文章已经说了如何安装社区版的Delphi和如何在unRaid下虚拟Windows)。

       在正式操作之前,首先说明下使用aliyun的DDNS的基本条件。  

      1、这个也是最最关键的必不可少的一步,就是你家必须要有公网IP,现在很多地方宽带供应商只给内网IP(特别是移动和联通),如果是电信的话,可以打10000号免费开通公网IP功能,至于给不给你开,看各地的政策。反正我家一直是公网IP。

      2、你得有个阿里云的域名,如果没有,赶紧去阿里云上去买个,不差钱的可以买个.com、.cn的,如果只是玩玩的可以买个.xyz等超级便宜的域名。至于腾讯云的域名,有两种方法:你可以把域名改为阿里云托管,也可以将下面的代码稍微改动改成腾讯云的。因为我只有阿里云的域名,所以没法测试腾讯云的,这里就不改动了

      3、获得阿里云的访问key和访问key的密钥。这个在你登录阿里云后,在头像上点击鼠标左键会弹出来一个菜单,就可以直接看到。这里也给个建议,最好不要用主key和密钥,因为一旦泄露,也就是将你的阿里云的所有权限暴露给别人了,所以可以建立一个子用户key并且给他设置权限只能修改域名解析。

      4、提前在阿里云的域名管理上建立一个新的二级域名,虽然可以通过代码添加,但是我觉得自己手工添加会更安全,至少不会让自己的域名列表不经意的多出很多无用的二级域名。

       有了前面的几步,现在正是进入主题。代码写起来比较简单,只要根据阿里云的OpenAPI的文档编写就可以,当然你得学会看懂文档。很快我就将所有的代码完成,但是运行的时候一直提示错误,最主要错误就是一直提示签名值错误,这个签名值在我的理解也就是校验值,用来校验你是否有权限访问和修改域名解析。然后慢慢调试,在查看文档和阿里云公开的代码,发现有几个大坑,而在文档上却没有说明,当然也可能和每个编程工具的处理有点小关系,现在将每个大坑一一的说明下:

       坑1:做签名值的时候,发现调试出来的签名值很长,几乎是官方计算出来的两到三倍,代码仔细看看没什么打问题,已经按照文档要求使用了URL编码后再进行HMAC_SHA1然后最后再进行BASE64编码,整个过程都没任何问题,而且编码函数经过测试也没问题。文档上也没有任何说明要做其他的编码或者转变。后来发现惊醒HMAC_SHA1编码的时候,一定要生成他的原始二进制数据,而我们正常情况下一般都是生成字符串数据,我了了个去!好了,现在生成的签名值已经和官方的签名值的长度一模一样。

Pascal

varsSignByte: TBytes;
begin// 构造用于签名的字符串Result := Format('GET&%s&%s', [TURI.URLEncode('/'), TURI.URLEncode(sCanonicalQueryString)]);// 使用HMAC-SHA1计算返回原始二进制数据sSignByte := THashSHA1.GetHMACAsBytes(Result, sKeyScret + '&');// 得到签名值Result := TURI.URLEncode(TNetEncoding.Base64.EncodeBytesToString(sSignByte));
end;

       坑2:这下自以为没问题了,但是生成的签名值一直没官方的不一样啊,编码前的原始字符串貌似也一模一样,就是生成的签名值确实另外一个值,无奈,只能又是一步一步调试并且每一步和官方的值做对比,突然发现,UTC时间经过URL编码后会把里面的时间分隔符的":"会编码上"%3A",在生成签名值前,再进行一次URL编码,官方的会把签名的“%3A”里面的"%"再进行一次编码,变成了"%253A",而在Delphi中却没有编码,在我的理解中,确实不需要再编码了啊,因为这里已经按URL编码过一次了,这也算是一个坑吧,没办法,只能按照阿里云的要求,对其中的%再进行一次单独的替换。所以就有了下面代码的最后2行代码。

Pascal

  sBody := Format('AccessKeyId=%s&Action=DescribeSubDomainRecords&Format=json&SignatureMethod=HMAC-SHA1&', [sKey.Trim]);sBody := sBody + Format('SignatureNonce=%s&SignatureVersion=1.0&SubDomain=%s&', [GetNowStr, LowerCase(sDomain.Trim)]);sBody1 := sBody + Format('Timestamp=%s&Version=2015-01-09', [GetUTC]);sBody := sBody + Format('Timestamp=%s&Version=2015-01-09', [GetUTC.Replace('%', '%25')]);

       坑3:获取UTC时间,UTC时间也就是我们说的世界标准时间,正常我们获取UTC时间都是带毫秒的,这个一点问题都没有,而且也都是ISO8601的标准对时间进行格式化,但是我发现,在生成签名值的时候,和官方算出来的虽然一模一样,但是每次还是提示签名值不对,这个坑整整耗了我一天时间,百思不得其解。后来仔细对比,官方获取的UTC时间只精确到秒,而我获取的UTC时间是精确到毫秒,理论上这个没太大的关系,但是我尝试和官方同步使用精确到秒之后,这个问题竟然给解决了。

Pascal

function GetUTC: string;
// 获取ISO8601格式的UTC世界标准时间
varpTime: _TIME_ZONE_INFORMATION;
beginGetTimeZoneInformation(pTime);// 自带的函数带有毫秒,在Update的时候签名值出错,所以使用不带毫秒的时间// Result := TURI.URLEncode(DateToISO8601(IncMinute(Now, pTime.bias)));Result := TURI.URLEncode(FormatDateTime('YYYY-MM-DD''T''hh:nn:ss''Z''', IncMinute(Now, pTime.bias)));
end

       经过上面的填坑,很快整个代码都修改完成,其他的代码都是最最常规的,经过测试,已经很轻松的能够更新阿里云的域名解析了。加到自己的软件服务端,再也不需要安装官方或者其他的AliDDNS软件了。下面将所有的代码贴上,拿走不谢,如果有看官发现里面的错误或者有新的改动,希望能给个反馈哦。

Pascal

// *******************************************************
//
// AliDDNS - 阿里云DDNS自动更新代码
//
// 版权所有 (C) 2019-2020
// 作者:      HenryXu
// 更新日期:  2020.03.23
// 联系方式:QQ( 55524082 )
// 说明:
// 01、本函数只适用于AliYun的动态域名更新解析使用
// 02、域名必须在AliYun上注册
// 03、AliYun的AccessKeyID和AccessKeySecret可控制AliYun的所有权限,请妥善保管
// 04、建议开通子用户AccessKey,并设置只能修改云解析的权限
// 05、本函数待有优化之处,我会及时更新
// 06、有什么建议或修改其中的代码,希望能发一份给我以共同修改
//
// *******************************************************unit SQ_AliDDNS;interfaceusesWinapi.Windows, System.Classes, System.SysUtils, System.JSON, System.DateUtils, System.Hash, System.NetEncoding,System.Net.URLClient, System.Net.HttpClient, System.Net.HttpClientComponent, System.Net.Socket;{ -------------------------------------------------------------------------------过程名:    UpdateDomainRecord作者:      HenryXu日期:      2020.03.21参数:      sKey, sKeyScret, sDomain: string参数说明: AccessKeyID,AccessKeySecret,域名返回值:    string------------------------------------------------------------------------------- }
function UpdateDomainRecord(sKey, sKeyScret, sDomain: string): string;implementationconstApiAliAddr = 'http://alidns.aliyuncs.com/';PubIPAddr = 'http://ip.3322.net/';typeTSubDomainInfo = recordRR: string;TTL: string;IP: string;RecordId: string;Output: string;end;function GetUTC: string;
// 获取ISO8601格式的UTC世界标准时间
varpTime: _TIME_ZONE_INFORMATION;
beginGetTimeZoneInformation(pTime);// 自带的函数带有毫秒,在Update的时候签名值出错,所以使用不带毫秒的时间// Result := TURI.URLEncode(DateToISO8601(IncMinute(Now, pTime.bias)));Result := TURI.URLEncode(FormatDateTime('YYYY-MM-DD''T''hh:nn:ss''Z''', IncMinute(Now, pTime.bias)));
end;function GetNowStr: string;
beginResult := FormatDateTime('YYYYMMDDhhnnss', Now);
end;function httpSendRequest(sBody: string): string;
// 发送Http请求
varFHttpClient: TNetHttpClient;
beginFHttpClient := TNetHttpClient.Create(nil);tryResult := FHttpClient.Get(sBody).ContentAsString(TEncoding.UTF8);finallyFHttpClient.Free;end;
end;function GetPublicIP: string;
// 获取本机的公网IP
beginResult := httpSendRequest(PubIPAddr).Trim;
end;function GetSignature(sCanonicalQueryString, sKeyScret: string): string;
varsSignByte: TBytes;
begin// 构造用于签名的字符串Result := Format('GET&%s&%s', [TURI.URLEncode('/'), TURI.URLEncode(sCanonicalQueryString)]);// 使用HMAC-SHA1计算返回原始二进制数据sSignByte := THashSHA1.GetHMACAsBytes(Result, sKeyScret + '&');// 得到签名值Result := TURI.URLEncode(TNetEncoding.Base64.EncodeBytesToString(sSignByte));
end;function GetDomainRecordID(sKey, sKeyScret, sDomain: string): TSubDomainInfo;
// 获取AliYun上对应域名的解析信息
varsBody, sBody1: string;oJson: TJSONObject;index: integer;
beginsBody := Format('AccessKeyId=%s&Action=DescribeSubDomainRecords&Format=json&SignatureMethod=HMAC-SHA1&', [sKey.Trim]);sBody := sBody + Format('SignatureNonce=%s&SignatureVersion=1.0&SubDomain=%s&', [GetNowStr, LowerCase(sDomain.Trim)]);sBody1 := sBody + Format('Timestamp=%s&Version=2015-01-09', [GetUTC]);sBody := sBody + Format('Timestamp=%s&Version=2015-01-09', [GetUTC.Replace('%', '%25')]);Result.Output := httpSendRequest(Format('%s?%s&Signature=%s', [ApiAliAddr, sBody1,GetSignature(sBody, sKeyScret.Trim)]));// 这里从结果中需要取RecordId,RR和TTL三个值oJson := TJSONObject.ParseJSONValue(Trim(Result.Output)) as TJSONObject;if oJson <> nil thenbegintryif oJson.TryGetValue('TotalCount', index) and (index > 0) thenbeginResult.RecordId := oJson.GetValue<String>('DomainRecords.Record[0].RecordId').Trim;Result.RR := oJson.GetValue<String>('DomainRecords.Record[0].RR').Trim;Result.IP := oJson.GetValue<String>('DomainRecords.Record[0].Value').Trim;Result.TTL := oJson.GetValue<String>('DomainRecords.Record[0].TTL').Trim;end;finallyoJson.Free;end;end;
end;function UpdateDomainRecord(sKey, sKeyScret, sDomain: string): string;
// 更新AliYun上对应域名的IP记录
varsBody, sBody1: string;SubdomainInfo: TSubDomainInfo;
beginif GetPublicIP = TIPAddress.LookupName(sDomain).Address thenExit;SubdomainInfo := GetDomainRecordID(sKey, sKeyScret, sDomain);if not SubdomainInfo.RecordId.IsEmpty then// 如果AliYun中存在对应域名的记录begin// 以防万一再次确认公网IP和域名记录里的IP是否一样if GetPublicIP = SubdomainInfo.IP thenExit;sBody := Format('AccessKeyId=%s&Action=UpdateDomainRecord&Format=json&RR=%s&', [sKey.Trim, SubdomainInfo.RR]);sBody := sBody + Format('RecordId=%s&SignatureMethod=HMAC-SHA1&', [SubdomainInfo.RecordId]);sBody := sBody + Format('SignatureNonce=%s&SignatureVersion=1.0&TTL=%s&', [GetNowStr, SubdomainInfo.TTL]);sBody1 := sBody + Format('Timestamp=%s&Type=A&Value=%s&Version=2015-01-09', [GetUTC, GetPublicIP]);sBody := sBody + Format('Timestamp=%s&Type=A&Value=%s&Version=2015-01-09',[GetUTC.Replace('%', '%25'), GetPublicIP]);Result := httpSendRequest(Format('%s?%s&Signature=%s', [ApiAliAddr, sBody1, GetSignature(sBody, sKeyScret.Trim)]));endelseResult := SubdomainInfo.Output;
end;end.

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/598384.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring Boot中进行分库编程

在Spring Boot中使用分库&#xff08;Sharding&#xff09;需要根据具体情况而定。分库是一种解决单数据库性能瓶颈和数据量大的解决方案&#xff0c;通过将数据分散到多个数据库中&#xff0c;可以提升数据库的读写性能和数据存储能力。但是&#xff0c;分库也会带来一些复杂性…

Java项目:109SpringBoot超市仓管系统

博主主页&#xff1a;Java旅途 简介&#xff1a;分享计算机知识、学习路线、系统源码及教程 文末获取源码 一、项目介绍 超市仓管系统基于SpringBootMybatis开发&#xff0c;系统使用shiro框架做权限安全控制&#xff0c;超级管理员登录系统后可根据自己的实际需求配角色&…

PostgreSQL 分区

由于大量数据存储在数据库同一张表中&#xff0c;后期性能和扩展会受到影响。所以需要进行表分区&#xff0c;因为它可以将大表分成较小的表&#xff0c;从而减少内存交换问题和表扫描&#xff0c;最终提高性能。庞大的数据集被分成更小的分区&#xff0c;更易于访问和管理。 …

入门Python数据分析最好的实战项目

本篇将继续上一篇数据分析之后进行数据挖掘建模预测&#xff0c;这两部分构成了一个简单的完整项目。结合两篇文章通过数据分析和挖掘的方法可以达到二手房屋价格预测的效果。 下面从特征工程开始讲述。 特征工程 特征工程包括的内容很多&#xff0c;有特征清洗&#xff0c;…

DevOps(9)

目录 45.如何在Linux中将一个文件附加到另一个文件&#xff1f; 46.解释如何使用终端找到文件&#xff1f; 47.解释如何使用终端创建文件夹&#xff1f; 48.解释如何使用终端查看文本文件&#xff1f; 49.解释如何在Ubuntu LAMP堆栈上启用curl&#xff1f; 50.解释如何在…

Spring Cloud Gateway 缓存区异常

目录 1、问题背景 2、分析源码过程 3、解决办法 最近在测试环境spring cloud gateway突然出现了异常&#xff0c;在这里记录一下&#xff0c;直接上干货 1、问题背景 测试环境spring cloud gateway遇到以下异常 DataBufferLimitException: Exceeded limit on max bytes t…

kali系统

概述 实际上它就是一个预安装了很多安全工具的Debian Linxu。 nmap&#xff08;Network Mapper&#xff09;扫描 nmap是一个免费开放的网络扫描和嗅探的工具包&#xff0c;也叫网络映射器。 nmap的强大之处在于简单易用。看一下nmap的基本功能&#xff1a; 探测一组主机是…

YOLOv5独家原创改进:新颖的Shape IoU结合 Inner-IoU,基于辅助边框的IoU损失的同时关注边界框本身的形状和尺度,小目标实现高效涨点

💡💡💡本文改进:一种新的Shape IoU方法结合 Inner-IoU,基于辅助边框的IoU损失的同时,更加关注边界框本身的形状和尺度来计算损失 💡💡💡对小目标检测涨点明显,在VisDrone2019、PASCAL VOC均有涨点 收录 YOLOv5原创自研 https://blog.csdn.net/m0_63774211/ca…

图像处理中的DCT变换

图像处理中的DCT变换 Discrete Cosine Transform&#xff0c;离散余弦变换。 来源及公式推导&#xff0c;可以查看下面链接&#xff0c;介绍的比较详细&#xff0c;这里就不再重复说明了&#xff1a; 详解离散余弦变换&#xff08;DCT&#xff09; - 知乎 (zhihu.com)DCT变换…

关于openssh 9.6 p1 版本ssh-keygen -t rsa报错解决—— 筑梦之路

背景说明 执行命令ssh-keygen -t rsa 提示unknown key type rsa 执行命令ssh-keygen -t dsa 提示unknown key type dsa 从官方文档中看到9.5以后ssh-keygen 默认使用Ed25519加密算法&#xff0c;详情请阅读&#xff1a;OpenSSH: Release Notes 从官方文档中可查&#xff0c;…

Excel如何将单元格设为文本

文章目录 一、打开excel文件二、选中单元格三、右键设置单元格格式四、设置界面选择文本后点确定五、其他问题 在caa开发过程中遇到从CATUnicodeString转成CString时&#xff0c;通过SetItemText写入将ID号写入单元格&#xff0c;无法保存ID号中的数字0&#xff0c;故将单元格格…

03 decision tree(决策树)

一、decision tree&#xff08;决策树&#xff09; 1. classification problems&#xff08;纯度&#xff09; i . entropy &#xff08;熵&#xff09; ​ 作用&#xff1a;衡量一组数据的纯度是否很纯 &#xff0c;当五五开时他的熵都是最高的&#xff0c;当全是或者都不是…

【计算机算法设计与分析】棋盘覆盖问题(C++_分治法)

文章目录 题目描述测试样例算法原理算法实现参考资料 题目描述 在一个 2 k 2 k 2^k \times 2^k 2k2k个方格组成的棋盘中&#xff0c;若恰有一个方格与其他方格不同&#xff0c;则称该方格为一个特殊方格&#xff0c;且称该棋盘为一个特殊棋盘。显然&#xff0c;特殊方格在棋…

机器视觉系统选型-线阵工业相机选型

线阵相机特点&#xff1a; 1.线阵相机使用的线扫描传感器通常只有一行感光单元&#xff08;少数彩色线阵使用三行感光单元的传感器&#xff09; 2.线阵相机每次只采集一行图像&#xff1b; 3.线阵相机每次只输出一行图像&#xff1b; 4.与传统的面阵相机相比&#xff0c;面阵扫…

mysql之CRUD和常见函数和UNION 和 UNION ALL

mysql之CRUD和常见函数和UNION 和 UNION ALL 一.CRUD1.创建&#xff08;Create&#xff09; - 插入数据2.读取&#xff08;Read&#xff09; - 查询数据3.更新&#xff08;Update&#xff09; - 修改数据4.删除&#xff08;Delete&#xff09; - 删除数据 二.函数1.字符串函数&…

并发程序设计--D4GDB调试多进程程序

使用GDB调试此程序 start后 10行进入father进程&#xff0c;接下来会一直在父进程不断执行程序 那么如何进入子进程&#xff1f; 此时杀死此程序进程 重新gdb调试&#xff0c;使用以下命令&#xff0c;再使用n&#xff0c;可进入子进程 那么如何同时调试父进程和子进程 上图可…

210.【2023年华为OD机试真题(C卷)】最多购买宝石数目(贪心算法实现-JavaPythonC++JS实现)

🚀点击这里可直接跳转到本专栏,可查阅顶置最新的华为OD机试宝典~ 本专栏所有题目均包含优质解题思路,高质量解题代码(Java&Python&C++&JS分别实现),详细代码讲解,助你深入学习,深度掌握! 文章目录 一. 题目-最多购买宝石数目二.解题思路三.题解代码Pytho…

Linux上创建IntelliJ IDEA的快捷方式

在Linux上创建IntelliJ IDEA的快捷方式可以通过以下步骤完成&#xff1a; 打开终端&#xff0c;进入要创建快捷方式的目录。例如&#xff0c;如果想将快捷方式放在桌面上&#xff0c;可以使用以下命令进入桌面目录&#xff1a; cd ~/Desktop使用文本编辑器创建一个新的.deskt…

年终护眼台灯哪个好用?适合学生备考的台灯推荐

最近临近寒假&#xff0c;就有好多家长们和高校学子们催我推荐护眼台灯&#xff0c;人眼对光是非常敏感的&#xff0c;特别是儿童青少年眼睛还在发育的状态来说&#xff0c;光线是至关重要的&#xff0c;于是这次选择的护眼台灯我都是经过亲自使用测试的。 但由于现在护眼台灯…

【kettle】pdi/data-integration 打开ktr文件报错“Unable to load step info from XML“

一、报错内容&#xff1a; Unable to load step info from XML step nodeorg.pentaho.di.core.exception.KettleXMLException: Unable to load step info from XMLat org.pentaho.commons.launcher.Launcher.main (Launcher.java:92)at java.lang.reflect.Method.invoke (Met…