本文提供的代码不依赖官方SDK,为方便读者理解,文章中提供的代码片段包含详细的注释。完整功能代码见文末,删减冗余注释和空行后不足180行。
应用背景
在开发一款跨境电商APP时,产品要求使用短信验证手机号的真实性,开发团队无脑选择了一直在用的腾讯云作为服务商。基于合规要求,分别申请了国内和国际短信签名及模板;这导致发送短信时需要根据国内和国际号码选择对应的签名及模板,才能确保正确下发验证信息。
经PHP开发团队评审,一致认为腾讯云SDK需要导入的代码文件过多,影响项目的可读性和可维护性。于是,决定自己封装一个简单的SDK,以便于项目成员快速上手。
腾讯云短信概述
腾讯云短信(Short Message Service,SMS)沉淀腾讯十多年短信服务技术和经验,为 QQ、微信等亿级平台和10万+客户提供快速灵活接入的高质量的国内短信与国际/港澳台短信服务。
- 国内短信验证秒级触达,99%到达率。
- 国际/港澳台短信覆盖全球200+国家/地区,稳定可靠。
构造腾讯云基础类
此基础类签名算法符合腾讯云APIv3规范,不仅适用于本文的短信发送类,也适用于其他腾讯云产品接口。可大大简化脱离官方SDK进行单一产品接口开发的难度。
- 获取受限的
secretId
和secretKey
请参阅 腾讯云短信-预设策略
/** * 腾讯云基础类 * @author rehiy * @url https://www.rehiy.com/post/527/ */ class QCloudBasic { /** * @var string 接口域名 */ protected $domain = 'tencentcloudapi.com';
/** * @var string 接口服务 */ protected $service = ''; /** * @var string 接口版本 */ protected $version = ''; /** * @var string SecretId */ protected $secretId = ''; /** * @var string SecretKey */ protected $secretKey = ''; /** * 请求接口 * @param string $region 地域 * @param string $action 操作名称 * @param array $data 请求参数 */ protected function post($region, $action, $data) { $timestamp = time(); $payload = json_encode($data, 320); $authorization = $this->signature($action, $payload, $timestamp); $headers = [ "Authorization: {$authorization}", "Content-Type: application/json; charset=utf-8", "Host: {$this->domain}", "X-TC-Action: {$action}", "X-TC-Timestamp: {$timestamp}", "X-TC-Version: {$this->version}", "X-TC-Region: {$region}" ]; return $this->httpRequest('POST', "https://{$this->domain}", $payload, $headers); } /** * 生成签名 * @param string $action 操作名称 * @param string $payload 请求参数 * @param number $timestamp 时间戳 */ protected function signature($action, $payload = '{}', $timestamp = 0) { $algorithm = 'TC3-HMAC-SHA256'; $date = gmdate('Y-m-d', $timestamp); $canonicalUri = '/'; $canonicalQuery = ''; $canonicalHeaders = implode("\n", [ 'content-type:application/json; charset=utf-8', 'host:' . $this->domain, 'x-tc-action:' . strtolower($action), '' ]); $signedHeaders = 'content-type;host;x-tc-action'; $canonicalRequest = implode("\n", [ 'POST', $canonicalUri, $canonicalQuery, $canonicalHeaders, $signedHeaders, hash('SHA256', $payload) ]); $credentialScope = "{$date}/{$this->service}/tc3_request"; $stringToSign = implode("\n", [ $algorithm, $timestamp, $credentialScope, hash('SHA256', $canonicalRequest), ]); $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); return implode(', ', [ "{$algorithm} Credential={$this->secretId}/{$credentialScope}", "SignedHeaders={$signedHeaders}", "Signature={$signature}" ]); } /** * 发送HTTP请求 * @param string $method 请求方式 * @param string $url 服务器地址 * @param array|string $data 数据 * @param array $header 请求头 */ protected function httpRequest($method, $url, $data = null, $header = null) { $ch = curl_init($url); curl_setopt($ch, CURLOPT_TIMEOUT, 25); curl_setopt($ch, CURLOPT_HEADER, false); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); curl_setopt($ch, CURLOPT_HTTPHEADER, $header ?? []); if ($data && $method == 'POST') { if (is_array($data)) { if (in_array('Content-Type: application/json', $header)) { $data = json_encode($data, 320); } else { $data = http_build_query($data); } } curl_setopt($ch, CURLOPT_POSTFIELDS, $data); } list($body, $errno, $error) = [ curl_exec($ch), curl_errno($ch), curl_error($ch), curl_close($ch) ]; if ($errno != 0) { throw new \Exception('CURL - ' . $error, $errno); } try { $res = json_decode($body, true); return $res['Response'] ?? ''; } catch (\Exception $e) { return false; } }
}
构造短信发送类
基于less is more
原则,该类仅封装了发送短信接口,初始化时应以数组形式传入secretId
、secretKey
、sdkAppId
、signName
、templateCode
等参数。
- 获取
sdkAppId
请参阅 腾讯云短信-创建应用 - 获取
signName
和templateCode
请参阅 腾讯云短信-使用须知
/**
-
腾讯云短信类
-
@author rehiy
@url https://www.rehiy.com/post/527/
/
class QCloudSms extends QCloudBasic
{
/*- @var string
*/
protected $service = 'sms';
/**
- @var string
*/
protected $version = '2021-01-11';
/**
- @var string
*/
protected $sdkAppId = '';
/**
- @var string 短信签名
*/
protected $signName = '';
/**
- @var string 短信模板CODE
*/
protected $templateCode = '';
/**
- 构造函数
- @param array $config
*/
public function __construct($config)
{
this->domain = this->service . '.' . $this->domain;
foreach (config as key => $value) {
this->key = $value;
}
}
/**
-
发送短信
-
@param array $phoneNumbers 必填项,手机号码列表
-
@param array $templateParam 假如模板中存在变量需要替换则为必填项
-
@param string $extendCode 可选项,7位上行短信扩展码
@param string $sessionContext 可选项,携带上下文信息,server 会原样返回
*/
public function send(phoneNumbers, templateParam, extendCode = '', sessionContext = '')
{
$data = [
'PhoneNumberSet' => $phoneNumbers,
'SmsSdkAppId' => $this->sdkAppId,
'SignName' => $this->signName,
'TemplateId' => $this->templateCode,
'TemplateParamSet' => $templateParam,
];if ($extendCode) {
data['ExtendCode'] = extendCode;
}
if ($sessionContext) {
data['SessionContext'] = sessionContext;
}return this->post('ap-guangzhou', 'SendSms', data);
}
}
- @var string
调用短信发送类
本节代码仅用于演示如何调用短信发送类发送短信,实际业务代码请根据项目需求编写。
- 请根据前面步骤获取的参数替换伪配置变量
c_china</code>和<code>c_global
的值
/**
发送短信
@param string $phone 手机号码
@param string $code 验证码
*/
function send_sms(phone, code)
{
// 国内短信配置
$c_china = [
'secretId' => 'AK----------------------',
'secretKey' => 'SK----------------------',
'sdkAppId' => '1400000000',
'signName' => '签名',
'templateCode' => '123456',
];// 国际短信配置
$c_global = [
'secretId' => 'AK----------------------',
'secretKey' => 'SK----------------------',
'sdkAppId' => '1400000000',
'signName' => '签名',
'templateCode' => '654321',
];// 国内号码去除+86
phone = preg_replace('/^\+86(\d{11})/', '$1', $phone);
config = strpos(phone, '+') === 0 ? c_global : c_china;// 发送短信并返回结果
return (new QCloudSms(config))->send([phone], [$code]);
}// 发送国内短信
print_r(
send_sms('+8613800000000', '123456')
);
// 发送国际短信
print_r(
send_sms('+100000000000', '123456')
);
完整代码
点击这里下载