PHP 实现腾讯云短信 API 的简化客户端

使用 TC3-HMAC-SHA256 签名调用短信 API,处理可选参数、TLS 验证与 API 错误。

前言

由于腾讯云官方的SDK需要引用一大堆库和文件,使用起来非常不方便,因此我根据腾讯云的签名方法,自己写了一个腾讯云的精简版PHP-SDK,这个SDK只需要一个php文件即可实现,里面包含了签名方法、请求方法,以及用于调用的方法。

这个SDK是以腾讯云短信服务为例,其他腾讯云服务只需要改一下里面的参数也可以用。

SDK代码

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
<?php

//腾讯云短信服务SDK

class QcloudSimple
{
    private $SecretId;
    private $SecretKey;
    private $endpoint = "sms.tencentcloudapi.com";
    //接口请求域名
    private $service = "sms";
    //服务名称
    private $version = "2021-01-11";
    //API版本号
    private $region = "ap-guangzhou";
    //地域参数

    /**
     * 初始化
     * @access public
     * @param  string $SecretId 腾讯云SecretId
     * @param  string $SecretKey 腾讯云SecretKey
     */
    public function __construct($SecretId,$SecretKey)
    {
        $this->SecretId = $SecretId;
        $this->SecretKey = $SecretKey;
    }
    /**
     * 短信发送
     * @access public
     * @param string|array $PhoneNumber 手机号
     * @param string $AppId 应用ID
     * @param string $TemplateId 模板ID
     * @param string|array|null $TemplateParam 模板参数
     * @param string|null $SignName 短信签名
     * @param string|null $ExtendCode 短信码号扩展号
     * @param string|null $SessionContext 用户的 session 内容
     * @param string|null $SenderId 国内短信无需填写该项
     * @return false|mixed
     */
    public function SendSms($PhoneNumber, $AppId, $TemplateId, $TemplateParam = null, $SignName = null, $ExtendCode = null, $SessionContext = null, $SenderId = null)
    {
        $action = 'SendSms';
        $phones = is_array($PhoneNumber) ? $PhoneNumber : [$PhoneNumber];
        $param = [
            'PhoneNumberSet' => array_values(array_map('strval', $phones)),
            'SmsSdkAppId' => (string) $AppId,
            'TemplateId' => (string) $TemplateId,
        ];
        if ($TemplateParam !== null && $TemplateParam !== '') {
            $values = is_array($TemplateParam) ? $TemplateParam : [$TemplateParam];
            $param['TemplateParamSet'] = array_values(array_map('strval', $values));
        }
        $optional = [
            'SignName' => $SignName,
            'ExtendCode' => $ExtendCode,
            'SessionContext' => $SessionContext,
            'SenderId' => $SenderId,
        ];
        foreach ($optional as $name => $value) {
            if ($value !== null && $value !== '') {
                $param[$name] = (string) $value;
            }
        }
        return $this->send_request($action, $param);
    }
    private function send_request($action, $param)
    {
        $payload = json_encode($param, JSON_UNESCAPED_UNICODE);
        if ($payload === false) {
            return false;
        }
        $time = time();
        $authorization = $this->generateSign($payload, $time);
        $header = ['Authorization: ' . $authorization, 'Content-Type: application/json; charset=utf-8', 'X-TC-Action: ' . $action, 'X-TC-Timestamp: ' . $time, 'X-TC-Version: ' . $this->version, 'X-TC-Region: ' . $this->region];
        return $this->curl_post($payload, $header);
    }
    private function generateSign($payload, $time)
    {
        $algorithm = "TC3-HMAC-SHA256";
        // step 1: build canonical request string
        $httpRequestMethod = "POST";
        $canonicalUri = "/";
        $canonicalQueryString = "";
        $canonicalHeaders = "content-type:application/json; charset=utf-8\n" . "host:" . $this->endpoint . "\n";
        $signedHeaders = "content-type;host";
        $hashedRequestPayload = hash("SHA256", $payload);
        $canonicalRequest = $httpRequestMethod . "\n" . $canonicalUri . "\n" . $canonicalQueryString . "\n" . $canonicalHeaders . "\n" . $signedHeaders . "\n" . $hashedRequestPayload;
        // step 2: build string to sign
        $date = gmdate("Y-m-d", $time);
        $credentialScope = $date . "/" . $this->service . "/tc3_request";
        $hashedCanonicalRequest = hash("SHA256", $canonicalRequest);
        $stringToSign = $algorithm . "\n" . $time . "\n" . $credentialScope . "\n" . $hashedCanonicalRequest;
        // step 3: sign string
        $secretDate = hash_hmac("SHA256", $date, "TC3" . $this->SecretKey, true);
        $secretService = hash_hmac("SHA256", $this->service, $secretDate, true);
        $secretSigning = hash_hmac("SHA256", "tc3_request", $secretService, true);
        $signature = hash_hmac("SHA256", $stringToSign, $secretSigning);
        // step 4: build authorization
        $authorization = $algorithm . " Credential=" . $this->SecretId . "/" . $credentialScope . ", SignedHeaders=content-type;host, Signature=" . $signature;
        return $authorization;
    }
    private function curl_post($payload, $header)
    {
        $url = 'https://' . $this->endpoint . '/';
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_TIMEOUT, 10);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
        $json = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);
        if ($httpCode == 200) {
            $arr = json_decode($json, true);
            return isset($arr['Response']) ? $arr['Response'] : false;
        } else {
            return false;
        }
    }
}

调用前检查

需要 PHP 的 cURL 扩展以及可用的系统 CA 证书。保留 TLS 校验;证书错误时修复 CA 配置,不要关闭校验。手机号使用 API 要求的国际区号格式,例如 +8613812345678。

false 表示传输或响应解析失败。返回数组时仍需检查 Error 字段,HTTP 200 不等于短信已发送成功。签名名称、模板和应用 ID 必须使用已审核且互相匹配的实际配置。

参考:腾讯云 SendSms API。

延伸阅读

使用 Hugo 构建
主题 Stack 由 Jimmy 设计