阿里云短信服务API类

<?php
/**
 *  ==================================================================
 *        文 件 名: DySms.php
 *        概    要: 阿里云短信服务API
 *        作    者: IT小强
 *        创建时间: 2018-03-27 14:00
 *        修改时间:
 *        copyright (c) 2016 - 2018 mail@xqitw.cn
 *  ==================================================================
 */
 
/**
 * 阿里云短信服务API
 * Class DySms
 */
class DySms
{
    /**
     * @var string - 阿里云短信服务API请求地址
     */
    private $apiUrl = 'dysmsapi.aliyuncs.com';
    
    /**
     * @var array - 配置信息
     */
    protected $config = [
        // fixme @var string - Access Key ID
        'access_key_id'     => '',
        // fixme @var string - Access Key Secret
        'access_key_secret' => '',
        // fixme @var string - 短信签名
        'sign_name'         => '',
    ];
    
    /**
     * @var array - 错误码列表
     */
    private $messageArray = [
        'OK'                              => '请求成功',
        'isp.RAM_PERMISSION_DENY'         => 'RAM权限DENY',
        'isv.OUT_OF_SERVICE'              => '业务停机',
        'isv.PRODUCT_UN_SUBSCRIPT'        => '未开通云通信产品的阿里云客户',
        'isv.PRODUCT_UNSUBSCRIBE'         => '产品未开通',
        'isv.ACCOUNT_NOT_EXISTS'          => '账户不存在',
        'isv.ACCOUNT_ABNORMAL'            => '账户异常',
        'isv.SMS_TEMPLATE_ILLEGAL'        => '短信模板不合法',
        'isv.SMS_SIGNATURE_ILLEGAL'       => '短信签名不合法',
        'isv.INVALID_PARAMETERS'          => '参数异常',
        'isp.SYSTEM_ERROR'                => '系统错误',
        'isv.MOBILE_NUMBER_ILLEGAL'       => '非法手机号',
        'isv.MOBILE_COUNT_OVER_LIMIT'     => '手机号码数量超过限制',
        'isv.TEMPLATE_MISSING_PARAMETERS' => '模板缺少变量',
        'isv.BUSINESS_LIMIT_CONTROL'      => '业务限流',
        'isv.INVALID_JSON_PARAM'          => 'JSON参数不合法,只接受字符串值',
        'isv.BLACK_KEY_CONTROL_LIMIT'     => '黑名单管控',
        'isv.PARAM_LENGTH_LIMIT'          => '参数超出长度限制',
        'isv.PARAM_NOT_SUPPORT_URL'       => '不支持URL',
        'isv.AMOUNT_NOT_ENOUGH'           => '账户余额不足'
    ];
    
    /**
     * DySms 构造函数.
     * @param array $config - 配置信息
     *  [
     *      // fixme @var string - Access Key ID (必须)
     *      'access_key_id'     => '',
     *      // fixme @var string - Access Key Secret (必须)
     *      'access_key_secret' => '',
     *      // fixme @var string - 短信签名 (必须)
     *      'sign_name'         => '',
     * ]
     * @throws \Exception
     */
    public function __construct($config = []) {
        parent::__construct($config);
        if (!isset($this->config['access_key_id']) || empty($this->config['access_key_id'])) {
            throw new \Exception('请先配置 Access Key ID');
        }
        if (!isset($this->config['access_key_secret']) || empty($this->config['access_key_secret'])) {
            throw new \Exception('请先配置 Access Key Secret');
        }
        if (!isset($this->config['sign_name']) || empty($this->config['sign_name'])) {
            throw new \Exception('请先配置 短信签名');
        }
    }
    
    /**
     * 发送单条短信
     * @param string $phone - 手机号
     * @param string $templateCode - 模板编号
     * @param array $templateParam - 模版参数
     * @param array $config - 其他配置项
     *      (选填)string out_id 流水号
     *      (选填)string sms_up_extend_code   上行短信扩展码, 扩展码字段控制在7位或以下,无特殊需求用户请忽略此字段
     *      (选填)bool security    是否使用https(默认不启用)
     *      (选填)string access_key_id - 不填时调用全局配置
     *      (选填)string access_key_secret - 不填时调用全局配置
     *      (选填)string sign_name - 不填时调用全局配置
     * @return bool
     */
    public function sendSms($phone, $templateCode, $templateParam = [], $config = []) {
        $params = [];
        // fixme 必填: 请参阅 https://ak-console.aliyun.com/ 取得您的AK信息
        $accessKeyId = $this->getArrayData('access_key_id', $config, $this->config['access_key_id']);
        $accessKeySecret = $this->getArrayData('access_key_secret', $config, $this->config['access_key_secret']);
        
        // fixme 必填: 短信接收号码
        $params['PhoneNumbers'] = $phone;
        
        // fixme 必填: 短信签名,应严格按"签名名称"填写,请参考: https://dysms.console.aliyun.com/dysms.htm#/develop/sign
        $params['SignName'] = $this->getArrayData('sign_name', $config, $this->config['sign_name']);
        
        // fixme 必填: 短信模板Code,应严格按"模板CODE"填写, 请参考: https://dysms.console.aliyun.com/dysms.htm#/develop/template
        $params['TemplateCode'] = $templateCode;
        
        // fixme 可选: 设置发送短信流水号
        $outId = $this->getArrayData('out_id', $config, false);
        if ($outId) {
            $params['OutId'] = $outId;
        }
        // fixme 可选: 上行短信扩展码, 扩展码字段控制在7位或以下,无特殊需求用户请忽略此字段
        $smsUpExtendCode = $this->getArrayData('sms_up_extend_code', $config, false);
        if ($smsUpExtendCode) {
            $params['SmsUpExtendCode'] = $smsUpExtendCode;
        }
        
        // fixme 可选: 设置模板参数, 假如模板中存在变量需要替换则为必填项
        if (is_array($templateParam) && count($templateParam) >= 1) {
            $params['TemplateParam'] = json_encode($templateParam, JSON_UNESCAPED_UNICODE);
        }
        
        // 发送请求
        $params = array_merge($params, [
            'RegionId' => 'cn-hangzhou',
            'Action'   => 'SendSms',
            'Version'  => '2017-05-25'
        ]);
        // 此处可能会抛出异常,注意catch
        $security = $this->getArrayData('security', $config, false);
        $re = $this->request($accessKeyId, $accessKeySecret, $this->apiUrl, $params, $security);
        return $this->resultHandle($re);
    }
    
    /**
     * 批量发送短信
     * @param array $phone - 手机号 eg:[13838385438,13738385438]
     * @param string $templateCode - 模板编号
     * @param array $templateParam - 模版参数(二维数组) eg:[['number' => '302546'], ['number' => '302546']]
     *      模版参数(一维数组) eg:['number' => '302546'] 表示多个手机号使用相同参数
     * @param array $config - 其他配置项
     *      (选填)string out_id 流水号
     *      (选填)array|string  sms_up_extend_code   上行短信扩展码, 扩展码字段控制在7位或以下,无特殊需求用户请忽略此字段
     *                   - 数组 ['3123','3213'] 或者字符串 '134531'
     *      (选填)bool security    是否使用https(默认不启用)
     *      (选填)string access_key_id - 不填时调用全局配置
     *      (选填)string access_key_secret - 不填时调用全局配置
     *      (选填)array|string sign_name - 不填时调用全局配置 - 短信签名 ['签名1','签名2'] 或者字符串 '相同的签名'
     * @return bool
     */
    public function sendBatchSms($phone, $templateCode, $templateParam = [], $config = []) {
        $params = [];
        // fixme 必填: 请参阅 https://ak-console.aliyun.com/ 取得您的AK信息
        $accessKeyId = $this->getArrayData('access_key_id', $config, $this->config['access_key_id']);
        $accessKeySecret = $this->getArrayData('access_key_secret', $config, $this->config['access_key_secret']);
        
        // fixme 必填: 待发送手机号。支持JSON格式的批量调用,批量上限为100个手机号码,批量调用相对于单条调用及时性稍有延迟,验证码类型的短信推荐使用单条调用的方式
        $params['PhoneNumberJson'] = $phone;
        
        // fixme 必填: 短信签名,支持不同的号码发送不同的短信签名,每个签名都应严格按"签名名称"填写,请参考: https://dysms.console.aliyun.com/dysms.htm#/develop/sign
        $signName = $this->getArrayData('sign_name', $config, $this->config['sign_name']);
        if (is_string($signName)) {
            $signName = $this->setDataToArray($signName, count($phone));
        }
        $params['SignNameJson'] = $signName;
        
        // fixme 必填: 短信模板Code,应严格按"模板CODE"填写, 请参考: https://dysms.console.aliyun.com/dysms.htm#/develop/template
        $params['TemplateCode'] = $templateCode;
        
        // fixme 可选: 上行短信扩展码, 扩展码字段控制在7位或以下,无特殊需求用户请忽略此字段
        $smsUpExtendCode = $this->getArrayData('sms_up_extend_code', $config, []);
        if (is_string($smsUpExtendCode)) {
            $smsUpExtendCode = $this->setDataToArray($smsUpExtendCode, count($phone));
        }
        if (is_array($smsUpExtendCode) && count($smsUpExtendCode) >= 1) {
            $params['SmsUpExtendCodeJson'] = json_encode($smsUpExtendCode, JSON_UNESCAPED_UNICODE);
        }
        
        // fixme 必填: 模板中的变量替换JSON串,如模板内容为"亲爱的${name},您的验证码为${code}"时,此处的值为
        // 友情提示:如果JSON中需要带换行符,请参照标准的JSON协议对换行符的要求,比如短信内容中包含\r\n的情况在JSON中需要表示成\\r\\n,否则会导致JSON在服务端解析失败
        if (is_array($templateParam) && count($templateParam) >= 1) {
            if (!isset($templateParam[0]) || !is_array($templateParam[0])) {
                $templateParam = $this->setDataToArray($templateParam, count($phone));
            }
            $params['TemplateParamJson'] = json_encode($templateParam, JSON_UNESCAPED_UNICODE);
        }
        
        
        if (is_array($params['SignNameJson'])) {
            $params['SignNameJson'] = json_encode($params['SignNameJson'], JSON_UNESCAPED_UNICODE);
        }
        if (is_array($params['PhoneNumberJson'])) {
            $params['PhoneNumberJson'] = json_encode($params['PhoneNumberJson'], JSON_UNESCAPED_UNICODE);
        }
        
        // 发送请求
        $params = array_merge($params, [
            'RegionId' => 'cn-hangzhou',
            'Action'   => 'SendBatchSms',
            'Version'  => '2017-05-25'
        ]);
        
        // 此处可能会抛出异常,注意catch
        $security = $this->getArrayData('security', $config, false);
        $re = $this->request($accessKeyId, $accessKeySecret, $this->apiUrl, $params, $security);
        return $this->resultHandle($re);
    }
    
    /**
     * 短信发送记录查询
     * @param string $phone - 手机号
     * @param string $date - 短信发送日期,格式Ymd,支持近30天记录查询
     * @param array $page - 分页信息
     *      page_size - 分页数
     *      current_page - 当前页
     * @param array $config - 其他配置项
     *      (选填)string out_id 流水号
     *      (选填)security    是否使用https(默认不启用)
     *      (选填)access_key_id - 不填时调用全局配置
     *      (选填)access_key_secret - 不填时调用全局配置
     * @return mixed
     */
    public function querySendDetails($phone, $date, $page = [], $config = []) {
        $params = [];
        // fixme 必填: 请参阅 https://ak-console.aliyun.com/ 取得您的AK信息
        $accessKeyId = $this->getArrayData('access_key_id', $config, $this->config['access_key_id']);
        $accessKeySecret = $this->getArrayData('access_key_secret', $config, $this->config['access_key_secret']);
        
        // fixme 必填: 短信接收号码
        $params['PhoneNumber'] = $phone;
        
        // fixme 必填: 短信发送日期,格式Ymd,支持近30天记录查询
        $params['SendDate'] = $date;
        
        // fixme 必填: 分页大小
        $params['PageSize'] = $this->getArrayData('page_size', $page, 10);
        
        // fixme 必填: 当前页码
        $params['CurrentPage'] = $this->getArrayData('current_page', $page, 1);
        
        // fixme 可选: 设置发送短信流水号
        $outId = $this->getArrayData('out_id', $config, false);
        if ($outId) {
            $params['BizId'] = $outId;
        }
        // 发送请求
        $params = array_merge($params, [
            'RegionId' => 'cn-hangzhou',
            'Action'   => 'QuerySendDetails',
            'Version'  => '2017-05-25'
        ]);
        
        // 此处可能会抛出异常,注意catch
        $security = $this->getArrayData('security', $config, false);
        return $this->request($accessKeyId, $accessKeySecret, $this->apiUrl, $params, $security);
    }
    
    /**
     * 返回结果处理
     * @param array|mixed $result - 原始数组
     * @return bool
     */
    private function resultHandle($result) {
        if (!is_array($result) || count($result) < 1 || !isset($result['Code'])) {
            $this->message = '请求失败,未知错误';
            return false;
        }
        if ($result['Code'] !== 'OK') {
            $this->message = $this->getArrayData($result['Code'], $this->messageArray, '请求失败,未知错误');
            return false;
        }
        $this->message = $this->getArrayData($result['Code'], $this->messageArray, '请求失败,未知错误');
        return true;
    }
    
    /**
     * 生成签名并发起请求
     * @param $accessKeyId string AccessKeyId (https://ak-console.aliyun.com/)
     * @param $accessKeySecret string AccessKeySecret
     * @param $domain string API接口所在域名
     * @param $params array API具体参数
     * @param $security boolean 使用https
     * @return bool|\stdClass 返回API接口调用结果,当发生错误时返回false
     */
    private function request($accessKeyId, $accessKeySecret, $domain, $params, $security = false) {
        $apiParams = array_merge([
            'SignatureMethod'  => 'HMAC-SHA1',
            'SignatureNonce'   => uniqid(mt_rand(0, 0xffff), true),
            'SignatureVersion' => '1.0',
            'AccessKeyId'      => $accessKeyId,
            'Timestamp'        => gmdate("Y-m-d\TH:i:s\Z"),
            'Format'           => 'JSON',
        ], $params);
        ksort($apiParams);
        
        $sortedQueryStringTmp = '';
        foreach ($apiParams as $key => $value) {
            $sortedQueryStringTmp .= '&' . $this->encode($key) . '=' . $this->encode($value);
        }
        
        $stringToSign = 'GET&%2F&' . $this->encode(substr($sortedQueryStringTmp, 1));
        
        $sign = base64_encode(hash_hmac('sha1', $stringToSign, $accessKeySecret . '&', true));
        
        $signature = $this->encode($sign);
        
        $url = ($security ? 'https' : 'http') . "://{$domain}/?Signature={$signature}{$sortedQueryStringTmp}";
        
        try {
            $content = $this->fetchContent($url);
            return json_decode($content, true);
        } catch (\Exception $e) {
            return false;
        }
    }
    
    /**
     * urlencode字符串编码
     * @param $str - 待编码字符串
     * @return null|string|string[]
     */
    private function encode($str) {
        $res = urlencode($str);
        $res = preg_replace("/\+/", "%20", $res);
        $res = preg_replace("/\*/", "%2A", $res);
        $res = preg_replace("/%7E/", "~", $res);
        return $res;
    }
    
    /**
     * 获取远程请求
     * @param $url - URL
     * @return mixed
     */
    private function fetchContent($url) {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_TIMEOUT, 5);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_HTTPHEADER, ["x-sdk-client" => "php/2.0.0"]);
        
        if (substr($url, 0, 5) == 'https') {
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        }
        
        $rtn = curl_exec($ch);
        
        if ($rtn === false) {
            trigger_error("[CURL_" . curl_errno($ch) . "]: " . curl_error($ch), E_USER_ERROR);
        }
        curl_close($ch);
        
        return $rtn;
    }
    
    /**
     * 同一数组重复放入数组
     * 'one string'----->  ['one string','one string','one string' ... ]
     * @param mixed $data - 待转换的数据
     * @param int $count - 数组长度
     * @return array - 返回转换好的数组
     */
    private function setDataToArray($data, $count) {
        $array = [];
        if ($count < 1) {
            return $array;
        }
        for ($i = 0; $i < $count; $i++) {
            $array[] = $data;
        }
        return $array;
    }
    
    /**
     * 生产随机字符串
     * @param int $length - 指定生产字符串的长度
     * @param string $type - 指定生产字符串的类型(all-全部,num-纯数字,letter-纯字母)
     * @return null|string
     */
    public function cmRound($length = 6, $type = 'num') {
        $str = '';
        $strUp = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
        $strLow = 'abcdefghijklmnopqrstuvwxyz';
        $number = '0123456789';
        switch ($type) {
            case 'num':
                $strPol = $number;
                break;
            case 'letter':
                $strPol = $strUp . $strLow;
                break;
            default:
                $strPol = $strUp . $number . $strLow;
        }
        $max = strlen($strPol) - 1;
        for ($i = 0; $i < $length; $i++) {
            $str .= $strPol[mt_rand(0, $max)];
        }
        return $str;
    }
}
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容