Drupal与百度云

 Drupal建站,免不了要和服务器厂商打交道。我们用过阿里云、腾讯云、百度云、亚马逊AWS云。

 

   最早买的云服务器是阿里云,我们现在也一直用它。后来买了几台腾讯云,感觉和阿里云差不多,价格要便宜一些。

 

   听一个技术朋友说,他用nodejs,把采集下来的数据存放到了百度BOS上面,非常方便,我便第一次试用了百度的BOS。去年的现在,因为一个网站要放到百度云上面运行,对百度云有了进一步的了解。在中国,访问量最大的Drupal网站(e.baidu.com),就是跑在百度云上面的。 因此我们也与百度云,结下了更深的联系。

 

    之所以,关注百度云,还有一个重点,就是百度云提供了多项服务,而这么几项服务,恰好正是我们所关注的重点。而阿里云,腾讯云,没有相关的云服务。

 

Drupal版本:

Drupal 与百度云语音合成(PHP SDK)的集成

作者:老葛 亚艾元软件

为客户开发一个语音提醒的功能,在党建o2o的指挥中心,如果有群众诉求提交,此时就会在党建O2O指挥中心的大屏上面,动态显示提示消息,并且支持语音的形式,通过声音提醒党建O2O指挥中心的负责人。

 

   对于实时提醒,我们采用nodejs服务器,基于Drupalnodejs的集成模块,做定制开发,初步实现了实时提醒。对于语音提醒,我们初步选择百度云里面的语音合成、识别接口。始终觉得,百度在AI方面,做的还是有特色的,特别是提出ALL in AI战略。语音接口也是百度AI的一部分。

 

   百度的语音接口,包含两部分,语音合成、语音识别。语音合成就是将文本转成语音文件;语音识别就是将语音文件转成文本,他们使用一个SDK。我们的语言采用的PHP,两个接口合二为一。

    百度语音文档地址:https://cloud.baidu.com/doc/SPEECH/index.html

    百度语音SDK下载地址:https://ai.baidu.com/sdk

   在百度后台,添加语音应用。我的账号已经认证,可以方便的添加,这个接口,初级阶段是免费的。所以也不用交钱。在管理界面的左边部分,有帮助文档,SDK下载的链接,很方便。我以前给百度工单里面提意见,说的就是帮助文档,下载地址,放到对应的接口的管理界面里面,开发的时候,好找。

 

   将下载的PHP SDK放到自己的模块目录下面,这是我封装好的一段代码。

 

function mycustom_get_audio_file($text, $filename) {

require_once 'AipSpeech.php';

 

// 你的 APPID AK SK

$app_id = '10xxxxxx';

$app_key = 'so81xxxxxxxxxxx';

$secret_key  = 'WoDXxxxxxxxxxxxxxxx';

 

$client = new AipSpeech($app_id, $app_key, $secret_key);

 

$result = $client->synthesis($text, 'zh', 1, array(

'vol' => 5,

));

     drupal_set_message('abc');

if(!is_array($result)){

 drupal_set_message('123');

//file_put_contents('audio.mp3', $result);

  $dir_uri = file_stream_wrapper_get_instance_by_uri('public://');

  $realpath = $dir_uri->realpath();

  $sudio_dir = 'public://baiduaudio';

  $return = file_prepare_directory($sudio_dir);

  if (empty($return)) {

drupal_mkdir('public://baiduaudio', 0777, TRUE);

  }

  $file_path = $realpath . "/baiduaudio/". $filename;

  drupal_set_message('filepath:' .  $file_path);

  file_put_contents($file_path, $result);

  $file_url = file_create_url('public://baiduaudio/' .$filename);

        return $file_url;   

}


return '';

}

 

   这个封装的函数,能够实现给一段文本,调用百度AI的语音接口,生成MP3文件,保存到本地,将文件路径返回给调用者。

   我测试了一下,就这样,就能用了,非常简单。百度云最大的进步就是直接提供了PHP版本的SDK,以前他们家的产品,都是只提供java和C和nodejs的,对于PHP开发者来说,有时候需要自己基于他们的rest API从头写,痛苦。

 

   在诉求状态发生变化的时候,使用nodejs,实时通知,这是服务器端的代码:

$message->data['description'] = '嘀嘀嘀,嘀嘀嘀,' . $sub_wechat .'刚刚受理一条' . $service_type .',请及时办结';  

 $text = $message->data['description'];

  $filename = 'shouli_' .$node->nid .'.pm3';

  $return = mycustom_get_audio_file($text,$filename);

  $message->data['audio_url'] = $return;    

  $message->callback = 'nodejsMycustom';

  nodejs_send_content_channel_message($message);

 

前端代码js

Drupal.Nodejs.callbacks.nodejsMycustom = {

  callback: function (message) {

  console.log(message);

var audio_url = message.data.audio_url || '';

if(audio_url != '') {

var audio = document.createElement("audio");

 audio.src = audio_url;

 audio.play();

 console.log(audio.src);

}

 

这段js代码,在前段,接收nodejs的实时通知消息,如果消息里面,包含了audio_url ,此时创建一个audio元素,调用它的播放功能。

这样就实现了客户要求的语音提醒功能了。

 


Drupal版本:

百度云SMS发短信接口 PHP版本SDK,非官方

打算使用百度的SMS云服务来发短信,主要是用来作为业务通知。不过网上没有示例代码,在CSDN上面看到一个人写的,试用了一下,不能工作。http://blog.csdn.net/thekeypoint/article/details/46771211。这是唯一的PHP资料。

 向百度官方提问,希望他们提供一个SDK。杳无音讯。后来我们决定自己写一个。

代码下载地址:

https://github.com/g089h515r806/baidusms

这是我封装后的调用代码:调用BiaduSmsClient示例代码:

<?php
 
include_once 'BaiduSmsClient.php';
header('Content-Type:text/html;charset=utf-8');
 
$config = array(
  'endPoint' => 'sms.bj.baidubce.com',
  'accessKey' => '615c2ed10718888888888888888',
  'secretAccessKey' => 'a6e71bcfb99999999999999999999',
);
 
$smsClient = new BaiduSmsClient($config);
 
$message = array(
  "invokeId" => "rMi6pVgB-sfaf-tsdf",
  "phoneNumber" => "1368888888888888",
  "templateCode" => "smsTpl:e7476asdfsdf0de19d04ae906",
  "contentVar" => array(
    "code" =>  "123abc1234",
  ),
);
 
$ret = $smsClient->sendMessage($message);
 
print var_export($ret, true);

 

 

封装了以后,使用百度SMS发送短息,就会像示例中那样方便。

核心代码BaiduSmsClient.php:

<?php
/*
* 百度官方没有提供官方的SMS SDK,这个SMS客户端由yaiyuan.com开发,作者:老葛
* sign.php为百度开发的文档,里面有错误。我做了修正。现在成功。
* 这个文件,采用GPL协议开源。
*
*/
include_once 'sign.php';
 
class BaiduSmsClient{
  //终端,默认为sms.bj.baidubce.com
  protected $endPoint;
  //AK
  protected $accessKey;
  //SK
  protected $secretAccessKey;
  
  /**
   * $config = array(
   *    'endPoint' => 'sms.bj.baidubce.com',
   *    'accessKey' => '618888888888888888888888',
   *    'secretAccessKey' => 'a6888888888888888888888888',
   *  );
   */  
  function __construct(array $config) { 
    $this->endPoint = isset($config['endPoint']) ? $config['endPoint'] : 'sms.bj.baidubce.com';
    $this->accessKey = isset($config['accessKey']) ? $config['accessKey'] : '';  
       $this->secretAccessKey = isset($config['secretAccessKey']) ? $config['secretAccessKey'] : '';
  }
  
  /**
   * $message = array(
   *     "invokeId" => "rMVbbbb-Cssdc-dfgg",          //你申请的签名ID
   *     "phoneNumber" => "1856666666",  //手机电话号码
   *     "templateCode" => "smsTpl:e747612asdadsasdasdasd",  //模板的唯一标识
   *     "contentVar" => array(
   *       "code" =>  "123abc1234",  //模板里面的key变量  ${key}
   *     ),
   *   );
   *
   *   返回一个数组:
   *   成功:array( 'code' => '1000', 'message' => '成功', 'requestId' => '45e1235-3b07-4421-83f8-cf4c74b1232c', )
   *   失败:array( 'requestId' => 'a1145bba-95c0-4341-83de-115d41741f0f', 'code' => '401', 'message' => '权限认证失败', )
   */
   
  public function sendMessage($message_array) {
    
       //生成json格式
       $json_data = json_encode($message_array);
       
       //生成签名
    $signer = new SampleSigner();
    $credentials = array("ak" => $this->accessKey,"sk" => $this->secretAccessKey);
    $httpMethod = "POST";
    $path = "/bce/v2/message";
    $params = array();
    $timestamp = new \DateTime();
    $timestamp->setTimezone(new \DateTimeZone("GMT"));  
    $datetime = $timestamp->format("Y-m-d\TH:i:s\Z");
    $datetime_gmt = $timestamp->format("D, d M Y H:i:s T");
       
    $headers = array("Host" => $this->endPoint);            
    $str_sha256 = hash('sha256', $json_data);
    $headers['x-bce-content-sha256'] = $str_sha256;
    $headers['Content-Length'] = strlen($json_data);
    $headers['Content-Type'] = "application/json";
    $headers['x-bce-date'] = $datetime;       
    $options = array(SignOption::TIMESTAMP => $timestamp, SignOption::HEADERS_TO_SIGN =>array('host', 'x-bce-content-sha256',),);
    $ret = $signer->sign($credentials, $httpMethod, $path, $headers, $params, $options);
    $headers_curl = array(
      'Content-Type:application/json',
      'Host:' . $this->endPoint,
      'x-bce-date:' . $datetime,
      'Content-Length:' . strlen($json_data),
      'x-bce-content-sha256:' . $str_sha256,
      'Authorization:' . $ret,
      "Accept-Encoding: gzip,deflate",
      'User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9) Gecko/2008052906 Firefox/3.0',
      'Date:' .$datetime_gmt,
    );
 
 
 
    //$url = 'http://sms.bj.baidubce.com/bce/v2/message';
       $url = 'http://' . $this->endPoint . $path;
    //$url = '/bce/v2/message';
    $curl = curl_init();
    curl_setopt($curl, CURLOPT_URL, $url);
    curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);  
    curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2); 
    //curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
    //curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
    curl_setopt($curl, CURLOPT_POST, 1);
    curl_setopt($curl, CURLOPT_POSTFIELDS, $json_data);
    //curl_setopt($curl, CURLOPT_HEADER, 1);
    //curl_setopt($curl,CURLOPT_PROXY,'127.0.0.1:8888');//设置代理服务器 
    curl_setopt($curl, CURLOPT_HTTPHEADER, $headers_curl);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
    $result = curl_exec($curl);
    $errorno = curl_errno($curl);
    curl_close($curl);
    //print var_export($result, true);
    return json_decode($result);
  }
。。。


 


代码下载地址:

https://github.com/g089h515r806/baidusms

 

Drupal版本:

百度云官方PHP 签名加密程序的bug

   (如果你也遇到了同样的问题,签名加密代码修正后的见我的Github。程序员,以发现他人的bug为荣,而且是这么重要公司的这么重要程序里面的这么严重的bug。)

     坑爹的百度,浪费了我两天的时间,花在了这个bug身上。对百度的印象,就是停留在经常打电话过来,问加入不加入他们的推广服务,不是啥好印象。不过总体觉得,百度的技术应该是可以的。所以很难想象,他们官方给的PHP示例加密程序,里面有bug

 

我是在集成百度云SMS接口的时候,发现的bug。由于百度没有提供SMSPHP版本的SDK,所以我先试用了一下javaSDK。测试成功。

package com.yaiyuan;

 

import java.util.HashMap;

import java.util.Map;

 

import com.baidubce.auth.DefaultBceCredentials;

import com.baidubce.services.sms.SmsClient;

import com.baidubce.services.sms.SmsClientConfiguration;

import com.baidubce.services.sms.model.SendMessageV2Request;

import com.baidubce.services.sms.model.SendMessageV2Response;

 

public class SMSTest {

    public static void main(String[] args) {

        // 相关参数定义

          

          

        String endPoint = "http://sms.bj.baidubce.com"; // SMS服务域名,可根据环境选择具体域名

        String accessKeyId = "615c2ed1777777777777777770";  // 发送账号安全认证的Access Key ID

        String secretAccessKy = "a6e71bcfb3b8888888888888b219755d"; // 发送账号安全认证的Secret Access Key

 

        // akskconfig

        SmsClientConfiguration config = new SmsClientConfiguration();

        config.setCredentials(new DefaultBceCredentials(accessKeyId, secretAccessKy));

        config.setEndpoint(endPoint);

        config.setProxyHost("127.0.0.1");

        config.setProxyPort(8888);       

 

        // 实例化发送客户端

        SmsClient smsClient = new SmsClient(config);

 

        // 定义请求参数

        String invokeId = "rMi6adsf-CmVg-tKmu"; // 发送使用签名的调用ID

        String phoneNumber = "18888888889"; // 要发送的手机号码(只能填写一个手机号)

        phoneNumber = args[0];

        String templateCode = "smsTpl:e7476122a1c24e3712312304ae906"; // 本次发送使用的模板Code

        Map<String, String> vars =

                new HashMap<String, String>(); // 若模板内容为:您的验证码是${code},${time}分钟内输入有效

        String content = args[1];

        vars.put("code", content);

       // vars.put("time", "30");

 

        //实例化请求对象

        SendMessageV2Request request = new SendMessageV2Request();

        request.withInvokeId(invokeId)

                .withPhoneNumber(phoneNumber)

                .withTemplateCode(templateCode)

                .withContentVar(vars);

 

        // 发送请求

        SendMessageV2Response response = smsClient.sendMessage(request);

 

        // 解析请求响应 response.isSuccess()true 表示成功

        if (response != null && response.isSuccess()) {

            //  submit success

        } else {

            // fail

        }

    }

}

 

这些代码是直接拷贝的百度的例子,运行成功。Java代码本地的运行成功,让我对于采用百度云SMS有了初步的信息。

最坏的一种情况是,我用javaservelet封装一下,PHP通过http请求javajava再去请求百度的SMS接口。这个我是会的。所以有了一个保底的,也就不怕了。

 

研究百度的API,将他们的API相关文档阅读了一遍,觉得做的不错。我也很想体验一下,通过底层代码,直接调用他们的API的方式。百度的加密措施,做的还是不错的。体现了他们的技术性。

根据他们的官方提示:

https://cloud.baidu.com/doc/Reference/AuthenticationMechanism.html

然后找到他们的PHP的加密示例代码:

https://cloud.baidu.com/doc/Reference/AuthenticationMechanism.html#Sample.20Code

从他们代码的写法上面来看,这是他们的PHP SDK里面的签名代码,拿了过来。里面的示例程序,调用的bj.bcebos.com。我想代码应该是成功的。
我下载到本地,不断的修改代码。完全改造为SMS的,然后生成加密的签名。生成了,可以工作。
我将签名过后的信息,放到header里面,POST过去,以json的形式,这里用的PHP的CURL。经过多次的测试,我确信,传过去的数据,该传的都传了。
百度返回过来的还是401,认证失败。为了保证数据的正确性,我试用了Fiddler进行监控:

主要是查看我Post的数据对不对,调试,调试再调试,头都大了。就是不行。能想到的都想到了。
最后,我将java程序的执行,也交给Fiddler进行代理,然后比较PHP,java里面的异同。得到类似于下面的数据,我给的这个数据只是一个示例,java的和这个结构一样:

 

POST http://sms.bj.baidubce.com/bce/v2/message HTTP/1.1

Accept: */*

Connection: Keep-Alive

Content-Type: application/json

Host: sms.bj.baidubce.com

x-bce-date: 2016-10-22T10:44:55Z

Content-Length: 150

x-bce-content-sha256: 1996b5868b91c104f25ea6d76ce4a0c4d499df2c2de784085907b7ce74b43d55

Authorization: bce-auth-v1/61232ed1071231238580f9123140/2016-10-22T10:44:55Z/1800/host/cc50e7cb690a5a05642e0238921e6bcd4b23f1d7c39f9384fa7ce0c3a255de3f

Accept-Encoding: gzip,deflate

User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9) Gecko/2008052906 Firefox/3.0

Date: Sat, 22 Oct 2016 10:44:55 GMT

 

{"invokeId":"rMasdf-CmVg-tKmu","phoneNumber":"18588882049","templateCode":"smsTpl:e7412312337b3b0de19d04ae906","contentVar":{"code":"abc1234"}}

   没有发现什么异常。
   我最后想到了一个办法,就是把PHP的参数,和java保持一模一样,然后比较两者直接的加密过后的数据是否一致:
   当我将两者之间的加密前的数据逐一保持一致的时候,我发现了百度官方PHP示例代码里面竟然有bug。有兴趣的可以看我下面的代码,里面包含很多的调试信息。具体的调试信息就不说了。Bug是这样的:
1,百度的HEADERS_TO_SIGN有一个默认值,百度以前早期的程序,比如BOS直接使用默认值就行了。所以不会发现bug
2,百度后期的一些产品,比如SMS,就需要自己去设置这个HEADERS_TO_SIGN,当我们自己去设置这个参数的时候,百度的加密程序,就不正常了。
 
百度作为这么大的一个公司,推出了很多的云服务,而且PHP是一个中小站长喜欢的语言。这么长的时间内,很多服务都没有PHP的SDK版本,官方没有,而且没有第三方的。我想,不是没有PHP程序员,尝试过这样的事情,而是尝试了,失败了。对于百度内部的程序员来说,如果熟悉他们的接口,为一个服务封装一个PHP的SDK供大家调用,最多也就花上一天的时间。但是,估计有人写过,但是遇到了同样的问题;对于外部第三方的程序员,应该也有人写过类似的接口,估计花了不少的时间,也没有搞定。这样一个bug,也不知道造成了百度多少个潜在客户的流失,浪费了多少个程序员的开发时间。
    百度把自己的PHP代码,封装成BaiduBce.phar文件,也非常的不人道。无法看到源代码,无法进行调试。百度的接口程序,本来就是给人调用的,竟然保留所有的权利,仅以apache协议开源,怎么也得以GPL开源啊。
    附件是调试过的代码,里面有大量的调试信息。
namespace BaiduBce\Auth;
 
class SignOption
{
    const EXPIRATION_IN_SECONDS = 'expirationInSeconds';
 
    const HEADERS_TO_SIGN = 'headersToSign';
 
    const TIMESTAMP = 'timestamp';
 
    const DEFAULT_EXPIRATION_IN_SECONDS = 1800;
 
    const MIN_EXPIRATION_IN_SECONDS = 300;
 
    const MAX_EXPIRATION_IN_SECONDS = 129600;
}
 
class HttpUtil
{
    // 根据RFC 3986,除了:
    //   1.大小写英文字符
    //   2.阿拉伯数字
    //   3.点'.'、波浪线'~'、减号'-'以及下划线'_'
    // 以外都要编码
    public static $PERCENT_ENCODED_STRINGS;
 
    //填充编码数组
    public static function __init()
    {
        HttpUtil::$PERCENT_ENCODED_STRINGS = array();
        for ($i = 0; $i < 256; ++$i) {
            HttpUtil::$PERCENT_ENCODED_STRINGS[$i] = sprintf("%%%02X", $i);
        }
 
        //a-z不编码
        foreach (range('a', 'z') as $ch) {
            HttpUtil::$PERCENT_ENCODED_STRINGS[ord($ch)] = $ch;
        }
 
        //A-Z不编码
        foreach (range('A', 'Z') as $ch) {
            HttpUtil::$PERCENT_ENCODED_STRINGS[ord($ch)] = $ch;
        }
 
        //0-9不编码
        foreach (range('0', '9') as $ch) {
            HttpUtil::$PERCENT_ENCODED_STRINGS[ord($ch)] = $ch;
        }
 
        //以下4个字符不编码
        HttpUtil::$PERCENT_ENCODED_STRINGS[ord('-')] = '-';
        HttpUtil::$PERCENT_ENCODED_STRINGS[ord('.')] = '.';
        HttpUtil::$PERCENT_ENCODED_STRINGS[ord('_')] = '_';
        HttpUtil::$PERCENT_ENCODED_STRINGS[ord('~')] = '~';
    }
 
    //在uri编码中不能对'/'编码
    public static function urlEncodeExceptSlash($path)
    {
        return str_replace("%2F", "/", HttpUtil::urlEncode($path));
    }
 
    //使用编码数组编码
    public static function urlEncode($value)
    {
        $result = '';
        for ($i = 0; $i < strlen($value); ++$i) {
            $result .= HttpUtil::$PERCENT_ENCODED_STRINGS[ord($value[$i])];
        }
        return $result;
    }
 
    //生成标准化QueryString
    public static function getCanonicalQueryString(array $parameters)
    {
        //没有参数,直接返回空串
        if (count($parameters) == 0) {
            return '';
        }
 
        $parameterStrings = array();
        foreach ($parameters as $k => $v) {
            //跳过Authorization字段
            if (strcasecmp('Authorization', $k) == 0) {
                continue;
            }
            if (!isset($k)) {
                throw new \InvalidArgumentException(
                    "parameter key should not be null"
                );
            }
            if (isset($v)) {
                //对于有值的,编码后放在=号两边
                $parameterStrings[] = HttpUtil::urlEncode($k)
                    . '=' . HttpUtil::urlEncode((string) $v);
            } else {
                //对于没有值的,只将key编码后放在=号的左边,右边留空
                $parameterStrings[] = HttpUtil::urlEncode($k) . '=';
            }
        }
        //按照字典序排序
        sort($parameterStrings);
 
        //使用'&'符号连接它们
        return implode('&', $parameterStrings);
    }
 
    //生成标准化uri
    public static function getCanonicalURIPath($path)
    {
        //空路径设置为'/'
        if (empty($path)) {
            return '/';
        } else {
            //所有的uri必须以'/'开头
            if ($path[0] == '/') {
                return HttpUtil::urlEncodeExceptSlash($path);
            } else {
                return '/' . HttpUtil::urlEncodeExceptSlash($path);
            }
        }
    }
 
    //生成标准化http请求头串
    public static function getCanonicalHeaders($headers)
    {
            //print 'getCanonicalHeaders:'.var_export($headers, true);
        //如果没有headers,则返回空串
        if (count($headers) == 0) {
            return '';
        }
 
        $headerStrings = array();
        foreach ($headers as $k => $v) {
            //跳过key为null的
            if ($k === null) {
                continue;
            }
            //如果value为null,则赋值为空串
            if ($v === null) {
                $v = '';
            }
            //trim后再encode,之后使用':'号连接起来
            $headerStrings[] = HttpUtil::urlEncode(strtolower(trim($k))) . ':' . HttpUtil::urlEncode(trim($v));
        }
        //字典序排序
        sort($headerStrings);
 
        //用'\n'把它们连接起来
        return implode("\n", $headerStrings);
    }
}
HttpUtil::__init();
 
 
class SampleSigner
{
 
    const BCE_AUTH_VERSION = "bce-auth-v1";
    const BCE_PREFIX = 'x-bce-';
 
    //不指定headersToSign情况下,默认签名http头,包括:
    //    1.host
    //    2.content-length
    //    3.content-type
    //    4.content-md5
    public static $defaultHeadersToSign;
 
    public static function  __init()
    {
        SampleSigner::$defaultHeadersToSign = array(
            "host",
            "content-length",
            "content-type",
            "content-md5",
        );
    }
 
    //签名函数
    public function sign(
        array $credentials,
        $httpMethod,
        $path,
        $headers,
        $params,
        $options = array()
    ) {
        //设定签名有效时间
        if (!isset($options[SignOption::EXPIRATION_IN_SECONDS])) {
            //默认值1800秒
            $expirationInSeconds = SignOption::DEFAULT_EXPIRATION_IN_SECONDS;
        } else {
            $expirationInSeconds = $options[SignOption::EXPIRATION_IN_SECONDS];
        }
 
        //解析ak sk
        $accessKeyId = $credentials['ak'];
        $secretAccessKey = $credentials['sk'];
 
        //设定时间戳,注意:如果自行指定时间戳需要为UTC时间
        if (!isset($options[SignOption::TIMESTAMP])) {
            //默认值当前时间
            $timestamp = new \DateTime();
        } else {
            $timestamp = $options[SignOption::TIMESTAMP];
        }
        $timestamp->setTimezone(new \DateTimeZone("GMT"));
 
        //生成authString
        $authString = SampleSigner::BCE_AUTH_VERSION . '/' . $accessKeyId . '/'
            . $timestamp->format("Y-m-d\TH:i:s\Z") . '/' . $expirationInSeconds;
 
        //使用sk和authString生成signKey
        $signingKey = hash_hmac('sha256', $authString, $secretAccessKey);
 
        //生成标准化URI
        $canonicalURI = HttpUtil::getCanonicalURIPath($path);
 
        //生成标准化QueryString
        $canonicalQueryString = HttpUtil::getCanonicalQueryString($params);
 
        //填充headersToSign,也就是指明哪些header参与签名
        $headersToSign = null;
        if (isset($options[SignOption::HEADERS_TO_SIGN])) {
            $headersToSign = $options[SignOption::HEADERS_TO_SIGN];
        }
 
        //生成标准化header
        $canonicalHeader = HttpUtil::getCanonicalHeaders(
            SampleSigner::getHeadersToSign($headers, $headersToSign)
        );
 
        //整理headersToSign,以';'号连接
        $signedHeaders = '';
        if ($headersToSign !== null) {
            $signedHeaders = strtolower(
                //trim(implode(";", array_keys($headersToSign)))
                               trim(implode(";", $headersToSign))
            );
        }
 
        //组成标准请求串
        $canonicalRequest = "$httpMethod\n$canonicalURI\n"
            . "$canonicalQueryString\n$canonicalHeader";
        //$canonicalRequest = "$httpMethod\n$canonicalURI\n\nhost:sms.bj.baidubce.com";
            //print var_export($canonicalRequest, true);
        //使用signKey和标准请求串完成签名
        $signature = hash_hmac('sha256', $canonicalRequest, $signingKey);
 
        //组成最终签名串
        $authorizationHeader = "$authString/$signedHeaders/$signature";
 
        return $authorizationHeader;
    }
 
    //根据headsToSign过滤应该参与签名的header
    public static function getHeadersToSign($headers, $headersToSign)
    {
        
           //print 'headers:' .var_export($headers, true);
            //print 'headersToSign:' .var_export($headersToSign, true);
        //value被trim后为空串的header不参与签名
        $filter_empty = function($v) {
            return trim((string) $v) !== '';
        };
        $headers = array_filter($headers, $filter_empty);
 
        //处理headers的key:去掉前后的空白并转化成小写
        $trim_and_lower = function($str){
            return strtolower(trim($str));
        };
        $temp = array();
        $process_keys = function($k, $v) use(&$temp, $trim_and_lower) {
            $temp[$trim_and_lower($k)] = $v;
        };
        array_map($process_keys, array_keys($headers), $headers);
               //array_map($process_keys, array_keys($headersToSign), $headersToSign);
        $headers = $temp;
         //print 'headers123:' .var_export($headers, true);
        //取出headers的key以备用
        $header_keys = array_keys($headers);
       // print 'header_keys:' .var_export($header_keys, true);
        $filtered_keys = null;
        if ($headersToSign !== null) {
            //如果有headersToSign,则根据headersToSign过滤
 
            //预处理headersToSign:去掉前后的空白并转化成小写
            $headersToSign = array_map($trim_and_lower, $headersToSign);
            //print 'headersToSign4321:' .var_export($headersToSign, true);
            //只选取在headersToSign里面的header
            $filtered_keys = array_intersect_key($header_keys, $headersToSign);
 
        } else {
            //如果没有headersToSign,则根据默认规则来选取headers
            $filter_by_default = function($k) {
                return SampleSigner::isDefaultHeaderToSign($k);
            };
            $filtered_keys = array_filter($header_keys, $filter_by_default);
        }
        //print 'headersToSign123:' .var_export($headersToSign, true);
               //print 'filtered_keys123:' .var_export($filtered_keys, true);
               //print 'headers4321:' .var_export($headers, true);
               //$filtered_keys = array('host');
        //返回需要参与签名的header
        return array_intersect_key($headers, array_flip($filtered_keys));
    }
 
    //检查header是不是默认参加签名的:
    //1.是host、content-type、content-md5、content-length之一
    //2.以x-bce开头
    public static function isDefaultHeaderToSign($header)
    {
        $header = strtolower(trim($header));
        if (in_array($header, SampleSigner::$defaultHeadersToSign)) {
            return true;
        }
        return substr_compare($header, SampleSigner::BCE_PREFIX, 0, strlen(SampleSigner::BCE_PREFIX)) == 0;
    }
}
SampleSigner::__init();
//header('Content-Type:text/html;charset=utf-8');
  $data = array(
    "invokeId" => "rMiasdaB-Casg-tKmu",
    "phoneNumber" => "18888888849",
    "templateCode" => "smsTpl:e74761231231230de19d04ae906",
    "contentVar" => array(
        "code" =>  "abc1234",
    ),
  
  );
  
  $json_data = json_encode($data);
 
//签名示范代码
$signer = new SampleSigner();
$credentials = array("ak" => "615c2ed17777777777a10740","sk" => "a6e71bcfb388888888888819755d");
$httpMethod = "POST";
$path = "/bce/v2/message";
 
//$params = array("partNumber" => 9, "uploadId" => "VXBsb2FkIElpZS5tMnRzIHVwbG9hZA");
$params = array();
$timestamp = new \DateTime();
$timestamp->setTimezone(new \DateTimeZone("GMT"));
  //$DateTimeZone = timezone_open ( 'Asia/Chongqing' );
   // $timestamp->setTimezone( $DateTimeZone );
//$time_int = strtotime("2016-10-21T09:55:22Z");
//$timestamp->setTimestamp($time_int);
   
   
$datetime = $timestamp->format("Y-m-d\TH:i:s\Z");
 
$datetime_gmt = $timestamp->format("D, d M Y H:i:s T");
 
//echo $datetime;
/*
$headers = array("Host" => "sms.bj.baidubce.com",
                "Content-Length" => strlen($json_data),
                "Content-Type" => "application/json",
                "x-bce-date" => $datetime);
                               */
$headers = array("Host" => "sms.bj.baidubce.com");
$str_sha256 = hash('sha256', $json_data);
$headers['x-bce-content-sha256'] = $str_sha256;
$headers['Content-Length'] = strlen($json_data);
$headers['Content-Type'] = "application/json";
$headers['x-bce-date'] = $datetime;
//$timestamp->setTimestamp(1430123029);
 
$options = array(SignOption::TIMESTAMP => $timestamp,
  //SignOption::EXPIRATION_IN_SECONDS => 1800,
  //SignOption::HEADERS_TO_SIGN =>$headers,
  /*
  SignOption::HEADERS_TO_SIGN =>array(
    'host',
        //'x-bce-date',
        //'Content-Length',
        'x-bce-content-sha256',
  ),
  */
);
$ret = $signer->sign($credentials, $httpMethod, $path, $headers, $params, $options);
//print $ret;
//print var_export($ret, true);
$headers['Authorization'] = $ret;
 
//print '\n' .$str_sha256;
$headers_curl = array(
  'Content-Type:application/json',
  'Host:sms.bj.baidubce.com',
  'x-bce-date:' . $datetime,
  'Content-Length:' . strlen($json_data),
  'x-bce-content-sha256:' . $str_sha256,
  'Authorization:' . $ret,
  "Accept-Encoding: gzip,deflate",
  'User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9) Gecko/2008052906 Firefox/3.0',
  'Date:' .$datetime_gmt,
);
 
 
 
$url = 'http://sms.bj.baidubce.com/bce/v2/message';
//$url = '/bce/v2/message';
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2);
//curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
//curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, $json_data);
curl_setopt($curl, CURLOPT_HEADER, 1);
curl_setopt($curl,CURLOPT_PROXY,'127.0.0.1:8888');//设置代理服务器
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers_curl);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
$result = curl_exec($curl);
$errorno = curl_errno($curl);
curl_close($curl);

print var_export($result, true);

 

Drupal版本: