// 阿里云短信接口 (未测试 非原创 来自 qq 群)

// 阿里云短信接口
unit uAliyunSms;

{

Author: MarkWu
Q Q: 77910086

}

interface

uses Classes, SysUtils, uSMSIntf, IdHTTP, flcHash, EncdDecd;

type
  TAliYunSms = class(TInterfacedObject, ISMSIntf)
  private
    FHttp: TIdHTTP;
    FURL: string;
    FAppKey, FAppSercret, FAppCode: string;
    FSmsHost, FPath, FMethod: string;
    FQueryList: TStringList;

    function GetSmsURL: string;
    procedure SetSmsURL(value: string);
    function GetSign(APath, AMethod, ASecret: string; headers, querys, signHeader: TStringList): string;
    function BuildHeader(headers, signHeaderPrefixList: TStringList): string;
    function IsHeaderToSign(headerName: string; signHeaderPrefixList: TStringList): Boolean;
    function BuildStringToSign(APath, AMethod, ASecret: string; headers, querys, signHeader: TStringList): string;
    procedure InitHttpRequest(AHost, APath, AMethod, ASecret: string; headers, querys, signHeader: TStringList);
    function BuildResource(path: string; querys: TStringList): string;
    function InitialBasicHeader(path, appKey, appSecret, method: string;
      headers, querys, signHeaderPrefixList: TStringList): TStringList;


  public
    function GetSms(aList: TStringList): string;

    function SendSms(aHttp: TIdHttp): string;

    constructor Create(Ahttp: TIdHTTP);
    destructor Destroy; override;
  end;

  function HmacSHA256(data,key: string): string; stdcall; external 'hmac.dll';

implementation

uses
  uPublic;

const
  //请求Header Accept
  HTTP_HEADER_ACCEPT: string = 'Accept';
  //请求Body内容MD5 Header
  HTTP_HEADER_CONTENT_MD5: string = 'Content-MD5';
  //请求Header Content-Type
  HTTP_HEADER_CONTENT_TYPE: string = 'Content-Type';
  //请求Header UserAgent
  HTTP_HEADER_USER_AGENT: string = 'User-Agent';
  //请求Header Date
  HTTP_HEADER_DATE: string = 'Date';


  //签名Header
  X_CA_SIGNATURE: string = 'X-Ca-Signature';
  //所有参与签名的Header
  X_CA_SIGNATURE_HEADERS: string = 'X-Ca-Signature-Headers';
  //请求时间戳
  X_CA_TIMESTAMP: string = 'X-Ca-Timestamp';
  //请求放重放Nonce,15分钟内保持唯一,建议使用UUID
  X_CA_NONCE: string = 'X-Ca-Nonce';
  //APP KEY
  X_CA_KEY: string = 'X-Ca-Key';
  //请求API所属Stage
  X_CA_STAGE: string = 'X-Ca-Stage';

  //参与签名的系统Header前缀,只有指定前缀的Header才会参与到签名中
  CA_HEADER_TO_SIGN_PREFIX_SYSTEM = 'X-Ca-';


  LF = #10;
  //分隔符1
  SPE1 = ',';
  //分隔符2
  SPE2 = ':';

  //连接符
  SPE3 = '&';

  //赋值符
  SPE4 = '=';

  //问号符
  SPE5 = '?';


  //默认请求超时时间,单位毫秒
  DEFAULT_TIMEOUT: Integer = 1000;

{ TAliyunSms }

constructor TAliYunSms.Create(Ahttp: TIdHTTP);
begin
  FHttp := Ahttp;      //FHttp.HTTPOptions := [];
  FHttp.HandleRedirects := true;
  FAppKey     := '23875185';
  FAppSercret := '7d379763c332be158ff253bcb61xxxxx';
  FAppCode    := '8592a9ec93fa47f59bd7661a5fdxxxxx';
  FSmsHost    := 'http://sms.market.alicloudapi.com';
  FPath       := '/singleSendSms';
  FMethod     := 'GET';

  FQueryList := TStringList.Create;
  FQueryList.Add('ParamString={"value":"MarkWu","couponno":"12345678"}');
  FQueryList.Add('RecNum=13760312656');
  FQueryList.Add('SignName=好好用软件');
  FQueryList.Add('TemplateCode=SMS_69000458');
end;

destructor TAliYunSms.Destroy;
begin
  FQueryList.Free;
  
  inherited;
end;


function TAliYunSms.SendSms(aHttp: TIdHttp): string;
var
  aHeader_Accept, aHeader_Content_Type: string;
  headers,headers2, signHeader: TStringList;
  sha256Str, res: string;
begin
  //FParamsList.Sort;
  aHeader_Accept := 'application/text; charset=utf-8';
  aHeader_Content_Type := 'application/text; charset=utf-8'; //'application/x-www-form-urlencoded; charset=UTF-8';

  //aHttp.Request.ContentType := aHeader_Content_Type;//'application/text; charset=utf-8';
  //aHttp.Request.Accept := 'application/text; charset=utf-8';
  headers := TStringList.Create;
  signHeader := TStringList.Create;
  try
    
    headers.Add(HTTP_HEADER_CONTENT_TYPE + '=' + aHeader_Content_Type);
    headers.Add(HTTP_HEADER_ACCEPT + '=' + aHeader_Accept);
    {
    headers.Add('X-Ca-Request-Mode=debug');
    headers.Add('Accept=' + aHeader_Accept);
    headers.Add('X-Ca-Key=' + FAppKey);
    headers.Add('X-Ca-Timestamp=' + IntToStr(GetTimestamp(Now)) );
    headers.Add('X-Ca-Nonce=' + GetGUID(GetGUID));
    headers.Add('X-Ca-Signature=' + GetSign(FPath, FMethod, FAppSercret, headers, FQueryList, signHeader) );
    headers.Sort;
    }

    signHeader.Add(X_CA_TIMESTAMP);
    //signHeader.Add('a-header1');
    //signHeader.Add('a-header2');

    headers2 := InitialBasicHeader(FPath, FAppKey, FAppSercret, FMethod, headers, FQueryList, signHeader);

    InitHttpRequest(FSmsHost, FPath, FMethod, FAppSercret, headers2, FQueryList, signHeader);
    //res :=  FHttp.Get(FURL);

  finally
    FreeAndNil(headers);
   // if headers2 <> nil then
   // FreeAndNil(headers2);
    FreeAndNil(signHeader);
  end;
end;

function TAliYunSms.GetSms(aList: TStringList): string;
begin

end;

function TAliYunSms.GetSmsURL: string;
begin
  Result := FPath;
end;


procedure TAliYunSms.SetSmsURL(value: string);
begin
  FPath := Value;
end;

function TAliYunSms.InitialBasicHeader(path, appKey, appSecret, method: string; headers, querys, signHeaderPrefixList: TStringList): TStringList;

begin
  if (headers = nil) then
      headers := TStringList.Create;

    headers.Add('X-Ca-Request-Mode=debug');
    //headers.Add('Accept=' + aHeader_Accept);

    //headers.Add('X-Ca-Timestamp=1495990994649');// + IntToStr(GetTimestamp(StrToDateTime('2017-05-27 22:12:50.993') )) );
    headers.Add('X-Ca-Timestamp=' + IntToStr(GetTimestamp( Now )) );

    headers.Add('X-Ca-Nonce=' + GetGUID(GetGUID));
    headers.Add('X-Ca-Key=' + appKey);

    headers.Add('X-Ca-Signature=' + GetSign(FPath, FMethod, FAppSercret, headers, FQueryList, signHeaderPrefixList) );

  //headers.Add(X_CA_TIMESTAMP + '=' + get);
  //防重放,协议层不能进行重试,否则会报NONCE被使用;如果需要协议层重试,请注释此行
  //headers.Add(SystemHeader.X_CA_NONCE, Guid.NewGuid().ToString());
  //headers.Add(SystemHeader.X_CA_KEY, appKey);
  //headers.Add(SystemHeader.X_CA_SIGNATURE, SignUtil.Sign(path, method, appSecret, headers, querys, bodys, signHeaderPrefixList));

  Result := headers;
end;


function UrlEncode(const ASrc: string): string;
const
  UnsafeChars = '*#%<>+ []{}"":,';  {do not localize}
var
  i: Integer;
begin
  Result := '';    {Do not Localize}
  for i := 1 to Length(ASrc) do begin
    if (AnsiPos(ASrc[i], UnsafeChars) > 0) or (ASrc[i]< #32) or (ASrc[i] > #127) then begin
      Result := Result + '%' + LowerCase(IntToHex(Ord(ASrc[i]), 2));  {do not localize}
    end else begin
      Result := Result + ASrc[i];
    end;
  end;
end;

procedure TAliYunSms.InitHttpRequest(AHost, APath, AMethod, ASecret: string; headers, querys, signHeader: TStringList);
var
  url: string;
  i: Integer;
  sbParams: string;
  sKey, sValue: string;
  res: string;
begin
  url := AHost;
  if APath <> '' then
    url := url + APath;

  sbParams := '';

  if (querys <> nil) and (0 < querys.Count) then
  begin
    for I := 0 to querys.Count - 1 do
    begin
      if sbParams <> '' then
      begin
        sbParams := sbParams + SPE3;
      end;

      sKey := querys.Names[I];
      sValue := querys.Values[sKey];
      if (sKey = '') and (sValue <> '') then
      begin
        sbParams := sbParams + UTF8Encode(sValue);
      end;

      if sKey <> '' then
      begin
        sbParams := sbParams + sKey + SPE4;
        if sValue <> '' then
        begin
          sbParams := sbParams + UrlEncode(UTF8Encode(sValue));
        end;
      end;
    end;
    if sbParams <> '' then
      url := url + SPE5 + sbParams;
  end;

  FURL := url;
  FHttp.ReadTimeout := DEFAULT_TIMEOUT;

  if ContainsKey(headers, 'Accept') then
    FHttp.Request.Accept := ListPop(headers, 'Accept');

  if ContainsKey(headers, 'Date') then
    FHttp.Request.Date := StrToDateTime(ListPop(headers, 'Date'));

  if ContainsKey(headers, HTTP_HEADER_CONTENT_TYPE) then
    FHttp.Request.ContentType := ListPop(headers, HTTP_HEADER_CONTENT_TYPE);
  headers.Sort;
  for I := 0 to headers.Count - 1 do
  begin
    FHttp.Request.CustomHeaders.Add(headers.Names[i] + ': ' + headers.Values[headers.Names[i]]);
  end;

  res := FHttp.Get(url);
end;

function TAliYunSms.GetSign(APath, AMethod, ASecret: string; headers, querys, signHeader: TStringList): string;
var
  sSign: WideString;
  abc: T256BitDigest;
  str, res: string;
  pt: PByteArray;
  I: Integer;
begin
  sSign := BuildStringToSign(APath, AMethod, ASecret, headers, querys, signHeader);
  //sSign := 'GET'#$A'application/text; charset=utf-8'#$A#$A'application/text; charset=utf-8'#$A#$A'X-Ca-Key:23875185'#$A'' + 'X-Ca-Nonce:12D01C4C-6501-4262-A236-9FC04B7F6740'#$A'X-Ca-Timestamp:1495894370993'#$A'/singleSendSms?ParamString={"value":"MarkWu","couponno":"12345678"}&RecNum=13760312656&SignName=好好用软件&TemplateCode=SMS_69000458';
  abc := (CalcHMAC_SHA256(ASecret, AnsiToUtf8(sSign))) ;
  //Result := SHA256DigestToHexW(abc);

  SetLength(str, 32);
  pt := @(abc.Bytes);
  Move(pt[0], str[1], 32);

  Result := EncodeString(str);
end;

function TAliYunSms.BuildStringToSign(APath, AMethod, ASecret: string; headers, querys, signHeader: TStringList): string;
var
  I: Integer;
begin
  Result := UpperCase(AMethod) + LF;

  I := headers.IndexOfName(HTTP_HEADER_ACCEPT);
  if (I > -1) and (headers.Values[headers.Names[I]] <> '') then
    Result := Result + headers.Values[headers.Names[I]];
  Result := Result + LF;

  I := headers.IndexOfName(HTTP_HEADER_CONTENT_MD5);
  if (I > -1) and (headers.Values[headers.Names[I]] <> '') then
    Result := Result + headers.Values[headers.Names[I]];
  Result := Result + LF;

  I := headers.IndexOfName(HTTP_HEADER_CONTENT_TYPE);
  if (I > -1) and (headers.Values[headers.Names[I]] <> '') then
    Result := Result + headers.Values[headers.Names[I]];
  Result := Result + LF;

  I := headers.IndexOfName(HTTP_HEADER_DATE);
  if (I > -1) and (headers.Values[headers.Names[I]] <> '') then
    Result := Result + headers.Values[headers.Names[I]];
  Result := Result + LF;

  Result := Result + BuildHeader(headers, signHeader);
  Result := Result + BuildResource(aPath, querys);
end;


// 构建待签名Http头
//  headers              请求中所有的Http头
//  signHeaderPrefixList 自定义参与签名Header前缀
function TAliYunSms.BuildHeader(headers, signHeaderPrefixList: TStringList): string;
var
  I: Integer;
  sKey, sValue: string;
  sSignHeaders: string;
begin
  //剔除X-Ca-Signature/X-Ca-Signature-Headers/Accept/Content-MD5/Content-Type/Date
  if (signHeaderPrefixList <> nil) and (signHeaderPrefixList.Count > 0) then
  begin
    I := signHeaderPrefixList.IndexOfName(X_CA_SIGNATURE);
    if I > -1 then
    signHeaderPrefixList.Delete(I);

    I := signHeaderPrefixList.IndexOfName(X_CA_SIGNATURE_HEADERS);
    if I > -1 then
    signHeaderPrefixList.Delete(I);


    I := signHeaderPrefixList.IndexOfName('Accept');
    if I > -1 then
    signHeaderPrefixList.Delete(I);

    I := signHeaderPrefixList.IndexOfName(HTTP_HEADER_CONTENT_MD5);
    if I > -1 then
    signHeaderPrefixList.Delete(I);


    I := signHeaderPrefixList.IndexOfName(HTTP_HEADER_CONTENT_TYPE);
    if I > -1 then
      signHeaderPrefixList.Delete(I);

    I := signHeaderPrefixList.IndexOfName(HTTP_HEADER_DATE);
    if I > -1 then
      signHeaderPrefixList.Delete(I);

  end;


  Result := '';
  sSignHeaders := '';
  if (headers <> nil ) and (headers.Count > 0) then
  begin
    headers.Sort;
    for I := 0 to headers.Count - 1 do
    begin
      sKey := headers.Names[i];
      sValue := headers.Values[sKey];
      if IsHeaderToSign(sKey, signHeaderPrefixList) then
      begin
        Result := Result + sKey + SPE2;
        if sValue <> '' then
          Result := Result + sValue;
        Result := Result + LF;

        if Length(sSignHeaders) > 0 then
        begin
          sSignHeaders := sSignHeaders + SPE1;
        end;
        sSignHeaders := sSignHeaders + sKey;
      end;
    end;

    headers.Add(X_CA_SIGNATURE_HEADERS + '=' + sSignHeaders);
  end;

end;

//  Http头是否参与签名
function TAliYunSms.IsHeaderToSign(headerName: string; signHeaderPrefixList: TStringList): Boolean;
var
  I: Integer;
begin
  Result := False;
  if headerName = '' then Exit;

  if Pos(CA_HEADER_TO_SIGN_PREFIX_SYSTEM, headerName) > 0 then
  begin
    Result := True;
    Exit;
  end;

  if (signHeaderPrefixList <> nil) and (signHeaderPrefixList.Count > 0) then
  begin
    for I := 0 to signHeaderPrefixList.Count - 1 do
    begin
      Result := signHeaderPrefixList.IndexOfName(headerName) > -1;
    end;
  end;
end;


// 构建待签名Path+Query+FormParams
function TAliYunSms.BuildResource(path: string; querys: TStringList): string;
var
  sKey, sValue: string;
  paramList: TStringList;
  I: Integer;
  sParams: string;
begin
  Result := '';
  if path <> '' then
    Result := Result + path;

  paramList := TStringList.Create;
  try
    //query参与签名
    if (querys <> nil ) and (querys.Count > 0) then
    begin
      for I := 0 to querys.Count - 1 do
      begin
        sKey := querys.Names[I];
        sValue := querys.Values[sKey];
        if sKey <> '' then
        begin
          paramList.Add(sKey + '=' + sValue);
        end;
      end;
    end;
    paramList.Sort;

    //body参与签名


    //参数Key
    for I := 0 to paramList.Count - 1 do
    begin
      sKey := querys.Names[I];

      if sKey <> '' then
      begin
        sValue := querys.Values[sKey];
        if Length(sParams) > 0 then
        begin
          sParams := sParams + SPE3;
        end;
        sParams := sParams + sKey;

        if sValue <> '' then
        begin
          sParams := sParams + SPE4 + sValue;
        end;
      end;
    end;

    if sParams <> '' then
    begin
      Result := Result + SPE5 + sParams;
    end;
    
  finally
    FreeAndNil(paramList);
  end;
end;



end.

 

转载于:https://my.oschina.net/u/582827/blog/3001220

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值