Kirin 2 years ago
parent
commit
e4a24c54b2

+ 14 - 16
app/api/controller/AuthController.php

@@ -153,8 +153,8 @@ class AuthController
             return app('json')->fail($time . '秒内有效');
         $code = rand(100000, 999999);
         $data['code'] = $code;
-        CacheService::set('code_' . $phone,$code,$time);
-        $res = SMSService::send( $phone, $data, 'VERIFICATION_CODE');
+        CacheService::set('code_' . $phone, $code, $time);
+        $res = ShortLetterRepositories::AliSend($phone, $data, 'VERIFICATION_CODE');
         return app('json')->success($res);
     }
 
@@ -184,24 +184,21 @@ class AuthController
         if ($registerStatus) return app('json')->success('注册成功');
         return app('json')->fail(User::getErrorInfo('注册失败'));
     }
-    
+
     public function registernew(Request $request)
     {
         list($account, $phone, $password, $spread_uid) = UtilService::postMore([['account', ''], ['phone', ''], ['password', ''],
-        ['spread', 0],        
+            ['spread', 0],
         ], $request, true);
         if (strlen(trim($password)) < 6 || strlen(trim($password)) > 16)
             return app('json')->fail('密码必须是在6到16位之间');
         if ($password == '123456') return app('json')->fail('密码太过简单,请输入较为复杂的密码');
-        if(empty($account))return app('json')->fail('账号不能为空');
-          
-        $rs =User::register($account, $password, $spread_uid,$phone);
-        if($rs)
-        {
+        if (empty($account)) return app('json')->fail('账号不能为空');
+
+        $rs = User::register($account, $password, $spread_uid, $phone);
+        if ($rs) {
             return app('json')->success('注册成功');
-        }
-        else
-        {
+        } else {
             return app('json')->fail(User::getErrorInfo('注册失败'));
         }
     }
@@ -232,15 +229,17 @@ class AuthController
         if ($resetStatus) return app('json')->success('修改成功');
         return app('json')->fail(User::getErrorInfo('修改失败'));
     }
+
     public function resetnew(Request $request)
     {
         list($account, $pwd, $password) = UtilService::postMore([['account', ''], ['pwd', ''], ['password', '']], $request, true);
-        if(!User::be(['account'=>$account,'pwd'=>md5($pwd)]))return app('json')->fail(User::getErrorInfo('原密码不对'));
+        if (!User::be(['account' => $account, 'pwd' => md5($pwd)])) return app('json')->fail(User::getErrorInfo('原密码不对'));
         if ($password == '123456') return app('json')->fail('密码太过简单,请输入较为复杂的密码');
         $resetStatus = User::reset($account, $password);
         if ($resetStatus) return app('json')->success('修改成功');
         return app('json')->fail(User::getErrorInfo('修改失败'));
     }
+
     /**
      * 手机号登录
      * @param Request $request
@@ -394,10 +393,9 @@ class AuthController
             $userInfo->account = $phone;
             $userInfo->phone = $phone;
         }
-        if (User::where('uid',$request->uid())->update($data)) {
+        if (User::where('uid', $request->uid())->update($data)) {
             return app('json')->success('绑定成功');
-        }
-        else
+        } else
             return app('json')->fail('绑定失败');
     }
 }

+ 2 - 1
config/sms.php

@@ -43,7 +43,8 @@ return [
         //阿里云
         'aliyun' => [
             'template_id' => [
-
+                //验证码
+                'VERIFICATION_CODE' => 'SMS_247480546',
             ]
         ]
     ]

+ 32 - 0
crmeb/repositories/ShortLetterRepositories.php

@@ -4,7 +4,10 @@ namespace crmeb\repositories;
 
 
 use app\admin\model\sms\SmsRecord;
+use crmeb\services\AlismsService;
 use crmeb\services\sms\Sms;
+use think\Config;
+use think\Exception;
 use think\facade\Log;
 
 /**
@@ -46,4 +49,33 @@ class ShortLetterRepositories
             return false;
         }
     }
+
+
+    /**
+     * 发送短信
+     * @param string $phone 手机号码
+     * @param array $data 模板替换内容
+     * @param string $template 模板编号
+     * @return bool|string
+     */
+    public static function AliSend(string $phone, array $data, string $template)
+    {
+        try {
+            $sms = new AlismsService(sys_config('sms_account', '', true), sys_config('sms_token', '', true));
+            $res = $sms->setAction('SendSms')->setOptions([
+                'PhoneNumbers' => $phone,
+                'SignName' => '满园春',
+                'TemplateCode' => Config::get('sms.stores.aliyun.template_id.' . $template, 'sms.stores.aliyun.template_id.DEFAULT'),
+                'TemplateParam' => json_encode($data),
+            ])->execute();
+            if ($res['Code'] != 'OK') {
+                return $res['Message'];
+            } else {
+                SmsRecord::sendRecord($phone, $data['code'], $template, '');
+            }
+            return true;
+        } catch (Exception $exception) {
+            return $exception->getMessage();
+        }
+    }
 }

+ 651 - 0
crmeb/services/AlismsService.php

@@ -0,0 +1,651 @@
+<?php
+
+namespace crmeb\services;
+
+use Exception;
+
+/**
+ * 阿里云 - 短信服务功能模块。
+ * Class Alisms
+ */
+class AlismsService
+{
+    /**
+     * @var string $accessKeyId 访问密钥 ID 。
+     */
+    private $accessKeyId;
+
+    /**
+     * @var string $accessKeySecret 访问密钥。
+     */
+    private $accessKeySecret;
+
+    /**
+     * 支持的 API 列表:
+     *   短信发送接口:
+     *     SendSms            发送短信。
+     *     SendBatchSms       批量发送短信。
+     *   短信查询接口:
+     *     QuerySendDetails   查询短信发送的状态。
+     *   签名申请接口:
+     *     AddSmsSign         调用短信 AddSmsSign 申请短信签名。
+     *     DeleteSmsSign      调用接口 DeleteSmsSign 删除短信签名。
+     *     QuerySmsSign       调用接口 QuerySmsSign 查询短信签名申请状态。
+     *     ModifySmsSign      调用接口 ModifySmsSign 修改未审核通过的短信签名,并重新提交审核。
+     *   模板申请接口:
+     *     ModifySmsTemplate  调用接口 ModifySmsTemplate 修改未通过审核的短信模板。
+     *     QuerySmsTemplate   调用接口 QuerySmsTemplate 查询短信模板的审核状态。
+     *     AddSmsTemplate     调用接口 AddSmsTemplate 申请短信模板。
+     *     DeleteSmsTemplate  调用接口 DeleteSmsTemplate 删除短信模板。
+     *   回执消息:
+     *     SmsReport          订阅 SmsReport 短信状态报告,获取短信发送状态。
+     *     SmsUp              订阅 SmsUp 上行短信消息,获取终端用户回复短信的内容。
+     *     SignSmsReport      订阅签名审核状态消息( SignSmsReport ),获取指定签名的审核状态。
+     *     TemplateSmsReport  订阅模板审核状态消息( TemplateSmsReport ),获取指定模板的审核状态。
+     *
+     * @var string $action API 的名称。
+     */
+    private $action;
+
+    /**
+     * @var array $endpoints 阿里云公网服务地址。
+     */
+    private $endpoints = [
+        'dysmsapi' => [
+            'global' => 'dysmsapi.aliyuncs.com',
+            'cn-hangzhou' => 'dysmsapi.aliyuncs.com',
+            'ap-southeast-1' => 'dysmsapi.ap-southeast-1.aliyuncs.com',
+        ],
+        'dybaseapi' => [
+            'global' => 'dybaseapi.aliyuncs.com',
+            'cn-hangzhou' => '1943695596114318.mns.cn-hangzhou.aliyuncs.com',  // http or https;
+        ],
+    ];
+
+    /**
+     * @var string $date_time_format 默认时间格式。
+     */
+    private $dateTimeFormat = 'Y-m-d\TH:i:s\Z';
+
+    /**
+     * @var string $signName 签名。
+     */
+    private $signName;
+
+    /**
+     * @var string $verifyPhoneTemplateCode 短信验证码对应的短信模板 ID 。
+     */
+    private $verifyPhoneTemplateCode;
+
+    /**
+     * @var string $verifyPhoneTemplateField 短信模板变量中的验证码变量名。
+     */
+    private $verifyPhoneTemplateField;
+
+    /**
+     * @var array $baseParams 公共请求参数。
+     */
+    private $baseParams = [
+        'AccessKeyId' => null,
+        'Action' => null,
+        'Format' => 'json',
+        'RegionId' => 'cn-hangzhou',
+        'SignatureMethod' => 'HMAC-SHA1',
+        'SignatureNonce' => null,
+        'SignatureVersion' => '1.0',
+        'Timestamp' => null,
+        'Version' => '2017-05-25',
+    ];
+
+    /**
+     * @var array $options 请求参数。
+     */
+    private $options = [];
+
+    /**
+     * AliDysms constructor.
+     *
+     * @param string $access_key_id 访问密钥 ID 。
+     * @param string $access_key_secret 访问密钥。
+     */
+    public function __construct($access_key_id = null, $access_key_secret = null)
+    {
+        // 在这里读取配置文件,初始化配置。
+        // $this->signName = '';
+        // $this->accessKeyId = '';
+        // $this->accessKeySecret = '';
+        // $this->verifyPhoneTemplateCode = '';
+        // $this->verifyPhoneTemplateField = '';
+
+        if ($access_key_id) {
+            $this->accessKeyId = $access_key_id;
+        }
+
+        if ($access_key_secret) {
+            $this->accessKeySecret = $access_key_secret;
+        }
+    }
+
+    /**
+     * 设置 API 的名称。
+     *
+     * @param string $action API 的名称。
+     *
+     * @return $this
+     */
+    public function setAction($action)
+    {
+        $this->action = $action;
+        return $this;
+    }
+
+    /**
+     * 设置参数。
+     *
+     * @param string $key 参数名。
+     * @param mixed $value 参数值。
+     *
+     * @return $this
+     */
+    public function setOption($key, $value)
+    {
+        $this->options[$key] = $value;
+        return $this;
+    }
+
+    /**
+     * 查看是否存在某参数。
+     *
+     * @param string $key 参数名。
+     *
+     * @return bool
+     */
+    public function hasOption($key)
+    {
+        return isset($this->options[$key]);
+    }
+
+    /**
+     * 批量设置参数。
+     *
+     * @param array $options 参数数组。
+     * @param bool $cover 是否覆盖。
+     *
+     * @return $this
+     */
+    public function setOptions($options, $cover = false)
+    {
+        if ($cover) {
+            foreach ($options as $k => $v) {
+                $this->options[$k] = $v;
+            }
+        } else {
+            $this->options = array_merge($this->options, $options);
+        }
+
+        return $this;
+    }
+
+    /**
+     * 执行请求。
+     *
+     * @return bool|mixed|string
+     * @throws Exception
+     */
+    public function execute()
+    {
+        $url = $this->endpoints['dysmsapi']['cn-hangzhou'];
+        $baseParams = $this->baseParams;
+
+        $baseParams['AccessKeyId'] = $this->accessKeyId;
+        $baseParams['Action'] = $this->action;
+        $baseParams['SignatureNonce'] = md5(uniqid(mt_rand(), true));
+        $baseParams['Timestamp'] = gmdate($this->dateTimeFormat);
+
+        // 如果请求参数中包含有公共参数中的字段,则保留请求参数中的字段。
+        $options = array_merge($this->options, $baseParams);
+
+        unset($options['Signature']);
+
+        $options['Signature'] = $this->computeSignature($options);
+
+        $ch = curl_init();
+        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
+
+        curl_setopt($ch, CURLOPT_URL, $url);
+        curl_setopt($ch, CURLOPT_FAILONERROR, false);
+        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+        curl_setopt($ch, CURLOPT_POSTFIELDS, $this->parsePostHttpBody($options));
+
+        curl_setopt($ch, CURLOPT_TIMEOUT, 80);
+        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
+
+        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
+        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
+
+        $http_headers = [
+            'Date:' . gmdate($this->dateTimeFormat),
+            'Accept:application/json',
+            'x-acs-signature-method:HMAC-SHA1',
+            'x-acs-signature-version:1.0',
+            'x-acs-region-id:cn-hangzhou',
+            'x-sdk-client:php/2.0.0',
+            'Content-MD5:' . base64_encode(md5(json_encode($options), true)),
+            // 'Content-Type:application/octet-stream;charset=utf-8',
+        ];
+
+        curl_setopt($ch, CURLOPT_HTTPHEADER, $http_headers);
+
+        $res = curl_exec($ch);
+        $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+
+        $errno = curl_errno($ch);
+        $error = curl_error($ch);
+
+        curl_close($ch);
+
+        if ($errno > 0) {
+            throw new Exception($error, $errno);
+        }
+
+        $res = json_decode($res, true);
+
+        if (200 != $status || 'OK' != $res['Code']) {
+            throw new Exception('ERR[' . $res['Code'] . ']: ' . $res['Message']);
+        }
+
+        // 重置请求参数,保证后续再次使用不会遗留前次的参数。
+        $this->options = [];
+
+        return $res;
+    }
+
+    /**
+     * 发送短信。
+     *
+     * @param array|string $phone_numbers 手机号码,支持多个号码,多个号码字符串以英文半角逗号( , )隔开,支持数组。
+     * @param string $template_code 短信模板 ID 。
+     * @param string/array|null $templateParam 短信模板变量对应的实际值,支持 json 字符串,如果传入数组,则进行 json 编码。
+     * @param string $sign_name 短信签名名称。
+     * @param null $out_id 外部流水扩展字段。
+     *
+     * @return bool|mixed|string
+     * @throws Exception
+     */
+    public function send($phone_numbers, $template_code, $template_param = null, $sign_name = null, $out_id = null)
+    {
+        $this->setAction('SendSms');
+
+        if (is_array($phone_numbers)) {
+            $phone_numbers = join(',', $phone_numbers);
+        }
+
+        $this->setOptions([
+            'PhoneNumbers' => $phone_numbers,
+            'TemplateCode' => $template_code,
+        ]);
+
+        if ($sign_name) {
+            $this->setOption('SignName', $sign_name);
+        }
+
+        if (!$this->hasOption('SignName')) {
+            $this->setOption('SignName', $this->signName);
+        }
+
+        if (is_string($template_param)) {
+            $this->setOption('TemplateParam', $template_param);
+        } elseif (is_array($template_param)) {
+            $this->setOption('TemplateParam', json_encode($template_param, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
+        }
+
+        if (is_string($out_id)) {
+            $this->setOption('OutId', $out_id);
+        }
+
+        return $this->execute();
+    }
+
+    /**
+     * 发送短信验证码。
+     *
+     * @param string $phone_number 手机号码。
+     * @param int $digit 短信验证码位数,默认 6 位。
+     * @param string $verify_code 验证码。
+     * @param string $template_code 短信模板 id 。
+     * @param string $field 验证码参数字段。
+     *
+     * @return bool|mixed|string
+     * @throws Exception
+     */
+    public function sendVerifyCode($phone_number, $digit = 6, $verify_code = null, $template_code = null, $field = null)
+    {
+        // 生成指定位数的短信验证码。
+        if (empty($verify_code)) {
+            $verify_code = '';
+
+            for ($i = 0; $i < intval($digit); $i++) $verify_code .= mt_rand(0, 9);
+        }
+
+        $template_code = empty($template_code) ? $this->verifyPhoneTemplateCode : $template_code;
+        $field = empty($field) ? $this->verifyPhoneTemplateField : $field;
+
+        $response = $this->send(
+            $phone_number,
+            $template_code,
+            [$field => $verify_code]
+        );
+
+        if ($response['code'] === 'OK' && $response['message'] === 'OK') {
+            $response['phone_number'] = $phone_number;
+            $response['verify_code'] = $verify_code;
+            return $response;
+        }
+
+        return false;
+    }
+
+    /**
+     * 查询短信发送记录和发送状态。
+     *
+     * @param string $phone_number 手机号码。
+     * @param string $send_date 日期格式,格式为 yyyyMMdd ,例如 20181225 ,可查询最近 30 天内的记录。
+     * @param int $current_page 当前页码。
+     * @param int $page_size 每页记录数量,取值范围为 1~50 。
+     * @param null|string $biz_id 发送回执 ID 。
+     *
+     * @return bool|mixed|string
+     * @throws Exception
+     */
+    public function getDetails($phone_number, $send_date, $current_page = 1, $page_size = 10, $biz_id = null)
+    {
+        $this->setAction('QuerySendDetails');
+
+        $this->setOptions([
+            'CurrentPage' => $current_page,
+            'PageSize' => $page_size,
+            'PhoneNumber' => $phone_number,
+            'SendDate' => $send_date,
+        ]);
+
+        if ($biz_id) {
+            $this->setOption('BizId', $biz_id);
+        }
+
+        return $this->execute();
+    }
+
+    /**
+     * 编辑短信签名。
+     *
+     * @param string $sign_name 签名名称。
+     * @param int $sign_source 签名来源。
+     * @param string $remark 短信签名申请说明。
+     * @param array $sign_file_list 签名的证明文件。
+     *
+     * @return bool|mixed|string
+     * @throws Exception
+     */
+    private function editSign($sign_name, $sign_source, $remark, $sign_file_list = [])
+    {
+        $this->setOptions([
+            'SignName' => $sign_name,
+            'SignSource' => $sign_source,
+            'Remark' => $remark,
+        ]);
+
+        if (is_array($sign_file_list)) {
+            $sign_file_list = array_values($sign_file_list);
+
+            foreach ($sign_file_list as $idx => $sign_file) {
+                $this->setOption('SignFileList.' . ($idx + 1) . '.FileSuffix', $sign_file['file_suffix']);
+                $this->setOption('SignFileList.' . ($idx + 1) . '.FileContents', $sign_file['file_contents']);
+            }
+        }
+
+        return $this->execute();
+    }
+
+    /**
+     * 申请短信签名。
+     *
+     * @param string $sign_name 签名名称。
+     * @param int $sign_source 签名来源。其中:
+     *
+     *                                      0:企事业单位的全称或简称。
+     *                                      1:工信部备案网站的全称或简称。
+     *                                      2:APP 应用的全称或简称。
+     *                                      3:公众号或小程序的全称或简称。
+     *                                      4:电商平台店铺名的全称或简称。
+     *                                      5:商标名的全称或简称
+     *
+     * @param string $remark 短信签名申请说明。
+     * @param array $sign_file_list 签名的证明文件。以数组形式传入,格式如下:
+     *
+     *    $sign_file_list = [
+     *       ['file_suffix' => 'jpg','file_contents' => 'R0lGOD...iwAA'],
+     *       ['file_suffix' => 'jpg','file_contents' => 'R0lGOD...iwAA'],
+     *       ['file_suffix' => 'jpg','file_contents' => 'R0lGOD...iwAA'],
+     *       ['file_suffix' => 'jpg','file_contents' => 'R0lGOD...iwAA'],
+     *    ];
+     *
+     * @return bool|mixed|string
+     * @throws Exception
+     */
+    public function addSign($sign_name, $sign_source, $remark, $sign_file_list = [])
+    {
+        $this->setAction('AddSmsSign');
+        return $this->editSign($sign_name, $sign_source, $remark, $sign_file_list);
+    }
+
+    /**
+     * 删除短信签名。
+     *
+     * @param string $sign_name 短信签名。
+     *
+     * @return bool|mixed|string
+     * @throws Exception
+     */
+    public function deleteSign($sign_name)
+    {
+        $this->setAction('DeleteSmsSign');
+        $this->setOption('SignName', $sign_name);
+        return $this->execute();
+    }
+
+    /**
+     * 修改未审核通过的短信签名,并重新提交审核。
+     *
+     * 参数格式参考 addSign 方法。
+     *
+     * @param string $sign_name 签名名称。
+     * @param int $sign_source 签名来源。
+     * @param string $remark 短信签名申请说明。
+     * @param array $sign_file_list 签名的证明文件。
+     *
+     * @return bool|mixed|string
+     * @throws Exception
+     */
+    public function modifySign($sign_name, $sign_source, $remark, $sign_file_list = [])
+    {
+        $this->setAction('ModifySmsSign');
+        return $this->editSign($sign_name, $sign_source, $remark, $sign_file_list);
+    }
+
+    /**
+     * 查询短信签名申请状态。
+     *
+     * @param string $sign_name 短信签名名称。
+     *
+     * @return bool|mixed|string
+     * @throws Exception
+     */
+    public function getSign($sign_name)
+    {
+        $this->setAction('QuerySmsSign');
+        $this->setOption('SignName', $sign_name);
+        return $this->execute();
+    }
+
+    /**
+     * 编辑短信模板。
+     *
+     * @param string $template_name 模板名称。
+     * @param int $template_type 短信类型。
+     * @param string $template_content 模板内容。
+     * @param string $remark 短信模板申请说明。
+     * @param string $template_code 短信模板 CODE 。
+     *
+     * @return bool|mixed|string
+     * @throws Exception
+     */
+    private function editTemplate($template_name, $template_type, $template_content, $remark, $template_code = null)
+    {
+        $this->setOptions([
+            'TemplateName' => $template_name,
+            'TemplateType' => intval($template_type),
+            'TemplateContent' => $template_content,
+            'Remark' => $remark,
+        ]);
+
+        if ($template_code) {
+            $this->setOption('TemplateCode', $template_code);
+        }
+
+        return $this->execute();
+    }
+
+    /**
+     * 申请短信模板。
+     *
+     * @param string $template_name 模板名称。
+     * @param int $template_type 短信类型。其中:
+     *
+     *                                  0:验证码。
+     *                                  1:短信通知。
+     *                                  2:推广短信。
+     *                                  3:国际/港澳台消息。
+     *
+     * @param string $template_content 模板内容,长度为 1~500 个字符。
+     * @param string $remark 短信模板申请说明。
+     *
+     * @return bool|mixed|string
+     * @throws Exception
+     */
+    public function addTemplate($template_name, $template_type, $template_content, $remark)
+    {
+        $this->setAction('AddSmsTemplate');
+        return $this->editTemplate($template_name, $template_type, $template_content, $remark);
+    }
+
+    /**
+     * 删除短信模板。
+     *
+     * @param string $template_code 短信模板 CODE 。
+     *
+     * @return bool|mixed|string
+     * @throws Exception
+     */
+    public function deleteTemplate($template_code)
+    {
+        $this->setAction('DeleteSmsTemplate');
+        $this->setOption('TemplateCode', $template_code);
+        return $this->execute();
+    }
+
+    /**
+     * 修改未通过审核的短信模板。
+     *
+     * 参数格式参考 addTemplate 方法。
+     *
+     * @param string $template_code 短信模板 CODE 。
+     * @param string $template_name 模板名称。
+     * @param int $template_type 短信类型。
+     * @param string $template_content 模板内容,长度为 1~500 个字符。
+     * @param string $remark 短信模板申请说明。
+     *
+     * @return bool|mixed|string
+     * @throws Exception
+     */
+    public function modifyTemplate($template_code, $template_name, $template_type, $template_content, $remark)
+    {
+        $this->setAction('ModifySmsTemplate');
+        return $this->editTemplate($template_name, $template_type, $template_content, $remark, $template_code);
+    }
+
+    /**
+     * 查询短信模板的审核状态。
+     *
+     * @param string $template_code 短信模板 CODE 。
+     *
+     * @return bool|mixed|string
+     * @throws Exception
+     */
+    public function getTemplate($template_code)
+    {
+        $this->setAction('QuerySmsTemplate');
+        $this->setOption('TemplateCode', $template_code);
+        return $this->execute();
+    }
+
+    /**
+     * 根据 POP 规则对要签名的字符串进行编码。
+     *
+     * @param string $str 要编码的字符串。
+     *
+     * @return string|string[]|null 已编码的字符串。
+     */
+    private function percentEncode($str)
+    {
+        $res = urlencode($str);
+        $res = preg_replace('/\+/', '%20', $res);
+        $res = preg_replace('/\*/', '%2A', $res);
+        $res = preg_replace('/%7E/', '~', $res);
+        return $res;
+    }
+
+    /**
+     * 计算签名。
+     *
+     * @param array $params 请求参数。
+     *
+     * @return string 签名。
+     */
+    private function computeSignature($params)
+    {
+        global $access_secret;
+
+        ksort($params);
+
+        $sourceArr = [];
+
+        foreach ($params as $k => $v) {
+            $sourceArr[] = $this->percentEncode($k) . '=' . $this->percentEncode($v);
+        }
+
+        $source = join('&', $sourceArr);
+
+        $source = 'POST' . '&' . $this->percentEncode('/') . '&' . $this->percentEncode($source);
+
+        return base64_encode(hash_hmac('sha1', $source, $this->accessKeySecret . '&', true));
+    }
+
+    /**
+     * 拼接请求体。
+     *
+     * @param array $params 请求参数。
+     *
+     * @return string 请求体字符串。
+     */
+    private function parsePostHttpBody($params)
+    {
+        $bodyArr = [];
+
+        foreach ($params as $k => $v) {
+            $bodyArr[] = $k . '=' . urlencode($v);
+        }
+
+        return join('&', $bodyArr);
+    }
+}
+