Delphi开发阿里短信接口
系统需要试用阿里短信接口发送多可文档系统短信通知,但阿里没有提供DELPHI代码示例。在构造签名字符串时遇到了编码和签名几个坑,经过努力解决了签名问题,如下代码给大家参考:
相关代码需要包含的头如下:
IdURI, IdGlobal, IdUriUtils, IdHMACSHA1, IdCoderMIME
// URL编码函数,阿里提供的JAVA是 java.net.URLEncoder.encode(value, "UTF-8").replace("+", "%20").replace("*", "%2A").replace("%7E", "~");
function SpecialUrlEncode(const ASrc: string; AByteEncoding: IIdTextEncoding = nil): string;
const
UnsafeChars = '&=*<>#%"{}|\^[]`:';
var
I, J, CharLen, ByteLen: Integer;
Buf: TIdBytes;
LChar: WideChar;
begin
Result := '';
Buf := nil;
if ASrc = '' then begin
Exit;
end;
EnsureEncoding(AByteEncoding, encUTF8);
SetLength(Buf, AByteEncoding.GetMaxByteCount(2));
I := 0;
while I < Length(ASrc) do begin
LChar := ASrc[I + 1];
if LChar = '+' then begin
Result := Result + '%20';
Inc(I);
end
else if WideCharIsInSet(UnsafeChars, LChar) or (Ord(LChar) < 33) or (Ord(LChar) > 127) then
begin
CharLen := CalcUTF16CharLength(ASrc, I + 1);
ByteLen := AByteEncoding.GetBytes(ASrc, I + 1, CharLen, Buf, 0);
for J := 0 to ByteLen - 1 do begin
Result := Result + '%' + IntToHex(Ord(Buf[J]), 2);
end;
Inc(I, CharLen);
end
else begin
Result := Result + Char(LChar);
Inc(I);
end;
end;
//
Result := StringReplace(Result, '%7E', '~', [rfReplaceAll, rfIgnoreCase]);
end;
// HA1 签名
function HMACSHA1(const AData, AKey: string): string;
var
LHMACSHA1: TIdHMACSHA1;
begin
LHMACSHA1 := TIdHMACSHA1.Create;
try
LHMACSHA1.Key := ToBytes(AKey);
Result := TIdEncoderMIME.EncodeBytes(LHMACSHA1.HashValue(ToBytes(AData)));
finally
LHMACSHA1.Free;
end;
end;
//
function LocalTimeZoneBias(): Integer;
var
TimeZoneInformation: TTimeZoneInformation;
Bias: Longint;
begin
case GetTimeZoneInformation(TimeZoneInformation) of
TIME_ZONE_ID_STANDARD:
Bias := TimeZoneInformation.Bias + TimeZoneInformation.StandardBias;
TIME_ZONE_ID_DAYLIGHT:
Bias := TimeZoneInformation.Bias + ((TimeZoneInformation.DaylightBias div 60) * -100);
else
Bias := TimeZoneInformation.Bias;
end;
Result := Bias;
end;
//GMT格式日期
function DateTimeToGMT(const DT: TDateTime): TDateTime;
begin
Result := DT + LocalTimeZoneBias() / 1440;
end;
(*
//阿里提供的JAVA示例
public static String sign(String accessSecret, String stringToSign) throws Exception {
javax.crypto.Mac mac = javax.crypto.Mac.getInstance("HmacSHA1");
mac.init(new javax.crypto.spec.SecretKeySpec(accessSecret.getBytes("UTF-8"), "HmacSHA1"));
byte[] signData = mac.doFinal(stringToSign.getBytes("UTF-8"));
return new sun.misc.BASE64Encoder().encode(signData);
}
*)
// 阿里的示例签名字符串
// GET&%2F&AccessKeyId%3DtestId%26Action%3DSendSms%26Format%3DXML%26OutId%3D123%26PhoneNumbers%3D15300000001%26RegionId%3Dcn-hangzhou%26SignName%3D%25E9%2598%25BF%25E9%2587%258C%25E4%25BA%2591%25E7%259F%25AD%25E4%25BF%25A1%25E6%25B5%258B%25E8%25AF%2595%25E4%25B8%2593%25E7%2594%25A8%26SignatureMethod%3DHMAC-SHA1%26SignatureNonce%3D45e25e9b-0a6f-4070-8c85-2956eda1b466%26SignatureVersion%3D1.0%26TemplateCode%3DSMS_71390007%26TemplateParam%3D%257B%2522customer%2522%253A%2522test%2522%257D%26Timestamp%3D2017-07-12T02%253A42%253A19Z%26Version%3D2017-05-25
// 签名结果为 zJDF+Lrzhj/ThnlvIToysFRq6t4=
// 构造待签名的请求串
function AliyunSignatureUrl(Mobile, TemplateId, TemplateParam, SignName, AccessKeyId, AccessKeySecret: string): string;
var
List: TStringList;
Df, Nonce, Ks, Sign: string;
I: Integer;
DT: TDateTime;
GUID: TGUID;
begin
CreateGuid(GUID);
Nonce := GUIDtoString(GUID);
Nonce := Copy(Nonce, 2, Length(Nonce) - 2);
//
DT := DateTimeToGMT(Now()); // 把本地时间转化为 GMT 时间
Df := FormatDateTime('yyyy-MM-dd', DT) + 'T' + FormatDateTime('HH:mm:ss', DT) + 'Z';
//
List := TStringList.Create();
try
// 短信参数
List.Append('PhoneNumbers=' + Mobile);
List.Append('SignName=' + SignName);
List.Append('TemplateCode=' + TemplateId);
List.Append('TemplateParam=' + TemplateParam);
List.Append('OutId=test-001');
// 公共请求参数
List.Append('AccessKeyId=' + AccessKeyId);
List.Append('Action=SendSms');
List.Append('Format=JSON');
List.Append('RegionId=cn-hangzhou');
List.Append('SignatureMethod=HMAC-SHA1');
List.Append('SignatureNonce=' + Nonce);
List.Append('SignatureVersion=1.0');
List.Append('Timestamp=' + Df);
List.Append('Version=2017-05-25');
// 参数Key排序
List.UseLocale := False;
List.CaseSensitive := True;
List.Sorted := True;
//
Ks := '';
for I := 0 to List.Count - 1 do begin
if I > 0 then
Ks := Ks + '&';
Ks := Ks + SpecialUrlEncode(List.Names[I]) + '=' + SpecialUrlEncode(List.ValueFromIndex[I]);
end;
// 数字签名
Sign := HMACSHA1('GET&%2F&' + SpecialUrlEncode(Ks), AccessKeySecret + '&');
Sign := SpecialUrlEncode(Sign);
//
Result := '?Signature=' + Sign + '&' + Ks;
finally
List.Free();
end;
end;
最后调用示例:
//手机号, 模板ID, 模板参数, 签名名称, 阿里短信AccessKeyId, 阿里短信AccessKeySecret
AliyunSignatureUrl('1390010001', 'Sms001999', '多可文档管理系统短信模板', '多可文档管理系统', 'AccessKeyId', 'AccessKeySecret');