Delphi 阿里云短信控件实现
一、数据结构定义
对于每一个函数,都有返回参数和专有参数,为此,专门定义了这些数据结构,这些参数的定义和阿里约定是完全一致的,包括变量名称,具体如下:
//发送短信返回结果记录
TRET_SendSms = record
BizId : string; //发送回执ID,可根据该ID在接口QuerySendDetails中查询具体的发送状态。
Code : string; //请求状态码。 返回OK代表请求成功。其他错误码详见错误码列表。
Message_ : string; //状态码的描述。
RequestId : string; //请求ID。
end;
//签名参数记录
TSignature_Param = record
ParamName : string; //参数名称
ParamValue : string; //参数值
end;
//签名参数记录列表
TSignature_Params = TArray<TSignature_Param>;
//查看新增签名状态的返回结果
TRET_QuerySmsSign = record
Code : String; //OK 请求状态码。返回OK代表请求成功。其他错误码详见错误码列表。
CreateDate : string; //2019-01-08 16:44:13 短信签名的创建日期和时间。
Message_ : String; //OK 状态码描述。
Reason : string; //文件不能证明信息真实性,请重新上传 审核备注。如果审核状态为审核通过或审核中,参数Reason显示为“无审核备注”。如果审核状态为审核未通过,参数Reason显示审核的具体原因。
RequestId : string; //CC89A90C-978F-46AC-B80D-54738371E7CA 请求ID。
SignName : String; //阿里云 短信签名。
SignStatus : integer; //1 签名审核状态。其中: 0:审核中。1:审核通过。2:审核失败,请在返回参数Reason中查看审核失败原因。
end;
//增加模板返回记录结构
TRET_AddSmsTemplate = record
Code : String; //OK 请求状态码。返回OK代表请求成功。其他错误码详见错误码列表。
Message_ : String; //OK 状态码的描述。
RequestId : String; //F655A8D5-B967-440B-8683-DAD6FF8DE990 请求ID。
TemplateCode: String; //SMS_152550005 短信模板CODE。您可以使用模板CODE通过API接口QuerySmsTemplate或在控制台查看模板申请状态和结果。
end;
//查询短信模板返回记录结构
TRET_QuerySmsTemplate = record
Code : String; //OK 请求状态码。返回OK代表请求成功。其他错误码详见错误码列表。
CreateDate : String; //2019-06-04 11:42:17 短信模板的创建日期和时间。
Message_ : string; //OK 状态码的描述。
Reason : String; //非验证码类型短信,请选择短信通知类型为推广短信。审核备注。如果审核状态为审核通过或审核中,参数Reason显示为“无审核备注”。如果审核状态为审核未通过,参数Reason显示审核的具体原因。
RequestId : String; //0A974B78-02BF-4C79-ADF3-90CFBA1B55B1 请求ID。
TemplateCode : String; //SMS_167035184 短信模板CODE。
TemplateContent : string;//亲爱的会员!阿里云短信服务祝您新年快乐!模板内容。
TemplateName : String; //阿里云短信测试模板 模板名称。
TemplateStatus: Integer; //1 模板审核状态。其中:0:审核中。1:审核通过。2:审核失败,请在返回参数Reason中查看审核失败原因。
TemplateType : Integer; //1 短信类型。其中:0:验证码。1:短信通知。2:推广短信。3:国际/港澳台消息。、
end;
//查询发送记录明细结构
TRET_SmsSendDetailDTO = record
Content : String; //【阿里云】验证码为:123,您正在登录,若非本人操作,请勿泄露短信内容。
ErrCode : string; //DELIVERED 运营商短信状态码。短信发送成功:DELIVERED。短信发送失败:失败错误码请参考错误码文档。
OutId : string; //123 外部流水扩展字段。
PhoneNum : String; //15200000000 接收短信的手机号码。
ReceiveDate : String; //2019-01-08 16:44:13 短信接收日期和时间。
SendDate : String; //2019-01-08 16:44:10 短信发送日期和时间。
SendStatus : Byte; //短信发送状态,包括:1:等待回执。2:发送失败。3:发送成功。
SendStatusMsg : string; //显示对应 SendStatus 的中文信息
TemplateCode : String; //SMS_122310183 短信模板ID。
end;
TRET_QuerySendDetails = record
Code : String; //OK 请求状态码。返回OK代表请求成功。其他错误码详见错误码列表。
Message_ : String; //OK 状态码的描述。
RequestId : String; //819BE656-D2E0-4858-8B21-B2E477085AAF 请求ID。
SmsSendDetailDTO : TArray<TRET_SmsSendDetailDTO>;
TotalCount: String; //1 短信发送总条数。
end;
//返回参数格式定义
TRet_Format = (JSON,XML);
二、控件公开属性定义
具体的参数定义如下:
published
//声明属性
property P_accessKeyId: string read FP_accessKeyId write Set_FP_accessKeyId;
property P_accessSecret: string read FP_accessSecret write Set_FP_accessSecret;
property P_Format: TRet_Format read FP_Format write Set_FP_Format default TRet_Format.JSON;
property P_RegionId: string read FP_RegionId write Set_FP_RegionId;
property P_SignatureMethod: string read FP_SignatureMethod write Set_FP_SignatureMethod;
property P_SignatureVersion: string read FP_SignatureVersion write Set_FP_SignatureVersion;
property P_Timestamp: string read FP_Timestamp;
property P_Version: string read FP_Version write Set_FP_Version;
property Ali_Host : string read FAli_Host write Set_FAli_Host;
end
三、控件私有方法说明(验签等需要)
- 初始化公共参数函数:init_PublicParams 初始化所有公共参数到 Signature_Params 数组中。
公共参数使用 TSignature_Param 记录数组进行保存。
procedure TAli_SMS_Component.init_PublicParams(var Signature_Params: TSignature_Params);
var
UTC_Time : TDateTime;
begin
//设置公共参数列表长度
SetLength(Signature_Params,8);
//1. //访问密钥 ID。AccessKey 用于调用 API。
Signature_Params[0].ParamName := 'AccessKeyId';
Signature_Params[0].ParamValue := FP_accessKeyId;
//2. 返回参数的语言类型。取值范围:json | xml。默认值:json。
Signature_Params[1].ParamName := 'Format';
if FP_Format = TRet_Format.JSON then
Signature_Params[1].ParamValue := 'JSON'
else
Signature_Params[1].ParamValue := 'XML';
//目前默认就只有 JSON
Signature_Params[1].ParamValue := 'JSON';
//3. API支持的RegionID,如短信API的值为:cn-hangzhou。
Signature_Params[2].ParamName := 'RegionId';
Signature_Params[2].ParamValue := FP_RegionId;
//4. 签名方式。取值范围:HMAC-SHA1。
Signature_Params[3].ParamName := 'SignatureMethod';
Signature_Params[3].ParamValue := FP_SignatureMethod;
//5. 签名唯一随机数。用于防止网络重放攻击,建议您每一次请求都使用不同的随机数。
FP_SignatureNonce := 'SMS_' + FormatDateTime('YYYY-MM-DD hh:mm:ss zzz',Now);
Signature_Params[4].ParamName := 'SignatureNonce';
Signature_Params[4].ParamValue := FP_SignatureNonce;
//6. 签名算法版本。取值范围:1.0
Signature_Params[5].ParamName := 'SignatureVersion';
Signature_Params[5].ParamValue := FP_SignatureVersion;
//7. 请求的时间戳。按照ISO8601 标准表示,并需要使用UTC时间,格式为yyyy-MM-ddTHH:mm:ssZ。示例:2018-01-01T12:00:00Z 表示北京时间 2018 年 01 月 01 日 20 点 00 分 00 秒。
UTC_Time := IncHour(Now,-8); //比北京时间晚8个小时
FP_Timestamp := FormatDateTime('YYYY-MM-DD',UTC_Time) + 'T' + FormatDateTime('hh:mm:ss',UTC_Time) + 'Z'; //请求的时间戳。按照ISO8601 标准表示,并需要使用UTC时间,格式为yyyy-MM-ddTHH:mm:ssZ。示例:2018-01-01T12:00:00Z 表示北京时间 2018 年 01 月 01 日 20 点 00 分 00 秒。
Signature_Params[6].ParamName := 'Timestamp';
Signature_Params[6].ParamValue := FP_Timestamp;
//8. API 的版本号,格式为 YYYY-MM-DD。取值范围:2017-05-25。
Signature_Params[7].ParamName := 'Version';
Signature_Params[7].ParamValue := FP_Version;
end;
- 参数列表进行按字典排序函数:Sort_Params
//对于参数列表进行按字典排序 Ascending_order 默认是升序排列
procedure TAli_SMS_Component.Sort_Params(var Signature_Params: TSignature_Params; Ascending_order: boolean);
var
i, j : Integer;
T : TSignature_Param;
begin
if Ascending_order then // 由大到校排序
begin
for i := low(Signature_Params) to high(Signature_Params) - 1 do
for j := low(Signature_Params) to high(Signature_Params) - i - 1 do
if Signature_Params[j].ParamName > Signature_Params[j + 1].ParamName then
begin
T := Signature_Params[j];
Signature_Params[j] := Signature_Params[j + 1];
Signature_Params[j + 1] := T;
end;
end // end if
else // 由小到大排序
begin
for i := low(Signature_Params) to high(Signature_Params) - 1 do
for j := low(Signature_Params) to high(Signature_Params) - i - 1 do
if Signature_Params[j].ParamName < Signature_Params[j + 1].ParamName then
begin
T := Signature_Params[j];
Signature_Params[j] := Signature_Params[j + 1];
Signature_Params[j + 1] := T;
end;
end; // end else
end;
- 特殊URL编码,就是阿里的POP编码函数:specialUrlEncode
function TAli_SMS_Component.specialUrlEncode(url: string): string;
begin
Result := TNetEncoding.URL.Encode(url);
Result := Result.Replace('+','%20').Replace('*','%2A').Replace('%7E','~');
end;
- 获取签名函数: Signature
{***********************************************************
入口参数:
Special_Params: 每条命令专有参数数组
HTTPMethod:命令的请求方法字符串,GET或者是POS
出口参数:
Query_Str:表示命令请求的实际字符串,发送请求的时候需要
Signature:请求参数获取的签名字符串,这个是关键!!!!!!
}
procedure TAli_SMS_Component.Get_Signature(
const Special_Params: TSignature_Params; HTTPMethod : string; var Query_Str, Signature: string);
var
i,count : integer;
URL_Str : string;
B : TBytes;
SMSParams: TSignature_Params;
PCount : integer;
begin
//1. 初始化公共参数
init_PublicParams(SMSParams);
//2. 初始化专有参数
PCount := Length(SMSParams);
count := length(Special_Params);
//2.1 设置参数数组的总长度
SetLength(SMSParams,PCount + count);
//2.2 把专有参数拼接到公共参数的后面
for i := 0 to count - 1 do
begin
SMSParams[i + PCount].ParamName := Special_Params[i].ParamName;
SMSParams[i + PCount].ParamValue := Special_Params[i].ParamValue;
end;
//3. 对所有参数进行排序,按照升序
Sort_Params(SMSParams);
//4. 组合成需要签名的字符串
count := length(SMSParams); //参数的总长度
Query_Str := '';
for i := 0 to count - 1 do
Query_Str := Query_Str + specialUrlEncode(SMSParams[i].ParamName) + '=' + specialUrlEncode(SMSParams[i].ParamValue) + '&';
//4.1
// 去除最后一个 &
Query_Str := Query_Str.Substring(0,Length(Query_Str) - 1);
//5. 按POP的签名规则拼接成最终的待签名串。
URL_Str := HTTPMethod + '&' + specialUrlEncode('/') + '&' + specialUrlEncode(Query_Str);
//6. 签名
//6.1进行数字签名 HmacSHA1
B := THashSHA1.GetHMACAsBytes(URL_Str,FP_accessSecret + '&');
//6.2进行base64
Signature := TNetEncoding.Base64.EncodeBytesToString(B);
end;
- 图片等文件进行Base64编码函数:Image_Base64
function TAli_SMS_Component.Image_Base64(FileName: string): string;
var
B : TBytes;
M : TMemoryStream;
begin
if not FileExists(FileName) then Exit('');
M := TMemoryStream.Create;
try
M.LoadFromFile(FileName);
M.Position := 0;
SetLength(B,M.Size);
M.Read(B[0],M.Size);
Result := TNetEncoding.Base64.EncodeBytesToString(B);
finally
M.Free;
end;
end;
四、短信控件需要引用的 Delphi 单元
uses
System.Hash,
system.DateUtils,
system.JSON,
System.Net.HttpClientComponent, {TNetHttpClient}
System.NetEncoding,
System.SysUtils,
System.Classes;