| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702 |
- <?php
- namespace app\common\library;
- use think\Cache;
- use app\common\model\Setting;
- use THink\Env;
- /**
- * 短信发送类
- *
- */
- class Sms
- {
- protected $_XML_string; // 提交服务商的请求数据
- protected $_accessKeyId = '';
- protected $_accessKeySecret = '';
- protected $_signName = "祈盟文化";
- protected $_signName2 = "祈盟文化";
- protected $_domain = 'dysmsapi.aliyuncs.com';
- protected $sp_code = ''; // 长号码,选填
- protected $uid = ""; // 业务标识,选填,由客户自行填写不超过20位的数字
- protected $_is_error = false; // 是否存在错误
- protected $_status = false;
- protected $_error_msg = null;
- protected $_last_time = null; // 最后一次发送短信的时间
- protected $_times = 0; // 同一号码当日发送的次数
- protected $_key = null; // redis缓存中保存的key
- protected $_mobile = null; // 发送的手机号
- public $_validate_content = 'SMS_462250586'; //'短信验证码:%d,有效期为10分钟,请勿向任何人提供您收到的短信验证码。'; // 验证码短信内容模板
- protected $_notify_content = '您的验证码为 ${code} ,该验证码3分钟内有效,请勿泄露于他人。'; // 通知短信内容模板
- protected $_appeal_fail_content = 'SMS_462250586'; //'【矩戮信息】您申诉的账号[%s]因提供的资料不完善而未通过,申诉编码为:[%s]。您可以稍后重新进行申诉,或联系矩戮信息客服咨询。';
- protected $_appeal_success_content = 'SMS_462250586'; //'【矩戮信息】您的账号[%s]的申诉已通过,申诉编码为:[%s], 成功凭证为:[%s]。请进入客服中心内的“申诉查询”http://www.srjl.net/kefu/sscx/,输入申诉编码和成功凭证后重置密码。';
- protected $_appeal_content = 'SMS_462250586'; //'您的账号:[%s]的申诉已受理,申诉编码号为:[%s],请妥善保存此申诉编码,以备后续使用。申诉结果将在%s个工作日内发送至您的手机,请勿重复申诉。申诉进度查询:http://www.srjl.net/kefu/sscx/'; //发送申诉编码内容模版
- protected $_settleaudit_fail_content = 'SMS_462250586'; //'您的账号[%s]的结算申请(订单编号为:[%s]),审核不通过。您可以登录后台查看原因,或联系星空互娱客服咨询。';
- protected $_settleaudit_success_content = 'SMS_462250586'; //'尊敬的会长,您的账号[%s],结算订单[%s]申请已通过,请及时在后台发起提现申请。有任何疑问请联系我们,谢谢!';
- protected $_identifyaudit_fail_content = 'SMS_462250586'; //'您的账号[%s]登记的身份认证信息,审核不通过。请尽快登录平台查看原因,或联系星空互娱客服咨询。';
- protected $_identifyaudit_success_content = 'SMS_462250586'; //'您的账号[%s]登记的身份认证信息,已审核通过。您可以登录后台查看。';
- protected $_code = null; // 六位数验证码
- protected $_redis = null; // redis连接实例
- protected $_expire_time = 180; // 过期时间
- protected $_interval = 60; // 发送间隔
- protected $_now_time = null; // 当前时间戳
- protected $_times_limit = 20; // 同一号码发送次数限制
- protected $_params = null; // 参数
- public function __construct()
- {
- $this->_key = 'clearable_sms:' . date('Ymd');
- $this->_redis = Cache::store('default');
- $this->_now_time= time();
- $this->_accessKeyId = Env::get('ali_oss.access_keysid');
- $this->_accessKeySecret = Env::get('ali_oss.access_keysecrt');
- }
- /**
- * 校验短信验证码是否正确,同时判断是否发送过,
- */
- public function checkCode($mobile, $code)
- {
- $this->_mobile = $mobile;
- if (!$this->_isMobile()) {
- $this->_is_error = true;
- $this->_error_msg = '手机号错误';
- }
- if (!$this->_is_error && (empty($this->_getLastTime()) || empty($this->_getCode()))) {
- $this->_is_error = true;
- $this->_error_msg = '短信验证码不存在,请先发送';
- }
- if (!$this->_is_error && ($this->_now_time - $this->_last_time > $this->_expire_time)) { // 判断是否过期
- $this->_is_error = true;
- $this->_error_msg = '验证码已失效';
- }
- if (!$this->_is_error && $code != $this->_code) {
- $this->_is_error = true;
- $this->_error_msg = '验证码输入错误';
- }
- if (!$this->_is_error) {
- $this->_status = true;
- $this->clearLastTime($mobile);
- }
- return array('status' => $this->_status, 'msg' => $this->_error_msg);
- }
- /**
- * 发送验证码
- * @param string $mobile
- * @return array('status'=>bool, 'msg'=>处理结果);
- */
- public function sendCode($mobile,$content='')
- {
- $this->_mobile = $mobile;
- $this->_sendRuler(); // 发送规则限制
- if (!$this->_is_error) {
- if (!empty($content)) $this->_validate_content = $content;
- $this->_formatString();
- $result = $this->sendSms($mobile,$this->_validate_content,$this->_params);
-
- $this->_error_msg = '';
- switch ($result->Code){
- case 'OK':
- break;
- case 'isv.DAY_LIMIT_CONTROL':
- $this->_error_msg = '短信每日发送上限';
- break;
- case 'isv.MONTH_LIMIT_CONTROL':
- $this->_error_msg = '短信每月发送上限';
- break;
- case 'isv.BUSINESS_LIMIT_CONTROL':
- $this->_error_msg = '短信每分钟内发送上限为一条';
- break;
- case 'isv.AMOUNT_NOT_ENOUGH':
- $this->_error_msg = '短信账户余额不足';
- break;
- }
-
- if($result && !$this->_error_msg) { // 发送成功,写入redis
- $this->_status = true;
- $this->_setRedisData();
- }else{
- $this->_is_error = true;
- }
- }
- return array('status' => $this->_status, 'msg' => $this->_error_msg);
- }
- /**
- * 发送申诉编码
- * @param string $mobile
- * @return array('status'=>bool, 'msg'=>处理结果);
- */
- public function sendAppealCode($username, $code, $mobile, $day, $certificate, $status)
- {
- $this->_formatAppealContent($username, $code, $day, $certificate, $status);
- $this->_mobile = $mobile;
- $this->_formatString('appeal');
- $result = $this->sendSms($mobile,$this->_appeal_content,$this->_params);
- if($result) { // 发送成功,写入redis
- $this->_status = true;
- $this->_setRedisData();
- }else{
- $this->_is_error = true;
- }
- return array('status' => $this->_status, 'msg' => $this->_error_msg);
- }
- /**
- * 发送结算审核通知
- * @param string $mobile
- * @return array('status'=>bool, 'msg'=>处理结果);
- */
- public function sendSettleAuditInfo($username, $mobile, $orderid, $status)
- {
- $this->_formatSettleAuditContent($username, $orderid, $status);
- $this->_mobile = $mobile;
- $this->_formatString('settleaudit');
- $result = $this->sendSms($mobile,$this->_settleaudit_content,$this->_params);
- if($result) { // 发送成功,写入redis
- $this->_status = true;
- $this->_setRedisData();
- }else{
- $this->_is_error = true;
- }
- return array('status' => $this->_status, 'msg' => $this->_error_msg);
- }
- /**
- * 发送身份认证审核通知
- * @param string $mobile
- * @return array('status'=>bool, 'msg'=>处理结果);
- */
- public function sendIdentifyAuditInfo($username, $mobile, $status)
- {
- $this->_formatIdentifyAuditContent($username, $status);
- $this->_mobile = $mobile;
- $this->_formatString('identifyaudit');
- $result = $this->sendSms($mobile,$this->_identifyaudit_content,$this->_params);
- if($result) { // 发送成功,写入redis
- $this->_status = true;
- $this->_setRedisData();
- }else{
- $this->_is_error = true;
- }
- return array('status' => $this->_status, 'msg' => $this->_error_msg);
- }
- /**
- * 发送通知短信
- * @param string $mobile 手机号
- * @param string $content 短信内容
- *
- * @return array('status'=>bool, 'msg'=>'处理结果');
- */
- public function sendNotify($mobile, $content, $params)
- {
- $this->_mobile = $mobile;
- //手机号码验证
- if (!$this->_isMobile()) {
- $this->_is_error = true;
- $this->_error_msg = '手机号错误';
- }
- if (!$this->_is_error) {
- $result = $this->sendSms($mobile,$content,$params);
- if($result) { // 发送成功,写入redis
- $this->_status = true;
- $this->_setRedisData();
- }else{
- $this->_is_error = true;
- }
- }
- return array('status' => $this->_status, 'msg' => $this->_error_msg);
- }
- /**
- * 清除最后发送时间,用于连续发送验证短信的场景
- */
- public function clearLastTime($mobile)
- {
- $this->_mobile = $mobile;
- if ($this->_redis->has($this->_key)) {
- $data = $this->_redis->get($this->_key);
- $data = json_decode($data, true);
- if (isset($data[ $mobile ]['sendtime'])) {
- $data[ $mobile ]['sendtime'] = 0;
- }
- $this->_redis->set($this->_key, json_encode($data), 86400);
- }
- }
- /**
- * 格式化短信内容
- * @param string $type 要发送的内容类型,validate为验证码,否则为通知
- * @param string $content 要发送的短息内容
- *
- * @return null
- */
- protected function _formatString($type = 'validate',$content='')
- {
- if ('validate' == $type) {
- $content = $this->_formatValidateContent();
- }
- elseif ('appeal' == $type) {
- $content = $this->_appeal_content;
- }
- /*
- // 签名
- $sign = md5($content.$this->_password);
- $data = array('cust_code'=>$this->_account,'sp_code'=>$this->sp_code,'content'=>$content,'destMobiles'=>$this->_mobile,'uid'=>$this->uid,'need_report'=>'yes','sign'=>$sign);
- $this->_XML_string = json_encode($data);
- */
- }
- /**
- * 格式化验证码短信内容
- * @return string 格式化后的内容
- */
- protected function _formatValidateContent()
- {
- $this->_makeCode();
- $this->_params = Array (
- "code" => $this->_code,
- );
- /*
- $content = sprintf($this->_validate_content, $this->_code);
- $this->_validate_content = $content;
- return $content;
- */
- }
- /**
- * 格式化结算审核结果短信内容
- * @return null
- */
- protected function _formatSettleAuditContent($username, $orderid, $status)
- {
- if ($status == 2) {
- $this->_settleaudit_content = $this->_settleaudit_fail_content;
- $this->_params = Array (
- "account" => $username,
- "orderid" => '******'.substr($orderid,-10),
- );
- } elseif ($status == 1) {
- $this->_settleaudit_content = $this->_settleaudit_success_content;
- $this->_params = Array (
- "account" => $username,
- "orderid" => '******'.substr($orderid,-10),
- );
- } else {
- $this->_settleaudit_content = $this->_settleaudit_fail_content;
- $this->_params = Array (
- "account" => $username,
- "orderid" => '******'.substr($orderid,-10),
- );
- }
- }
- /**
- * 格式化身份认证审核结果短信内容
- * @return null
- */
- protected function _formatIdentifyAuditContent($username, $status)
- {
- if ($status == 2) {
- $this->_identifyaudit_content = $this->_identifyaudit_fail_content;
- $this->_params = Array (
- "account" => $username,
- );
- } elseif ($status == 1) {
- $this->_identifyaudit_content = $this->_identifyaudit_success_content;
- $this->_params = Array (
- "account" => $username,
- );
- } else {
- $this->_identifyaudit_content = $this->_identifyaudit_fail_content;
- $this->_params = Array (
- "account" => $username,
- );
- }
- }
- /**
- * 格式化申诉编码短信内容
- * @return null
- */
- protected function _formatAppealContent($username, $code, $day, $certificate, $status)
- {
- if ($status == 2 || $status == 4) {
- $this->_appeal_content = $this->_appeal_fail_content;
- $this->_params = Array (
- "account" => $username,
- "number" => $code,
- );
- // $this->_appeal_content = sprintf($this->_appeal_fail_content, $username, $code);
- } elseif ($status == 1) {
- $this->_appeal_content = $this->_appeal_success_content;
- $this->_params = Array (
- "account" => $username,
- "number" => $code,
- "pincode" => $certificate,
- );
- // $this->_appeal_content = sprintf($this->_appeal_success_content, $username, $code, $certificate);
- } else {
- $this->_appeal_content = $this->_appeal_content;
- $this->_params = Array (
- "account" => $username,
- "number" => $code,
- "days" => $day,
- );
- // $this->_appeal_content = sprintf($this->_appeal_content, $username, $code, $day);
- }
- }
- /**
- * 发送验证码前规则验证
- * @return null
- */
- protected function _sendRuler()
- {
- $settingModel = new Setting();
- if ($this->_getTimes() >= $this->_times_limit) {
- $this->_is_error = true;
- $this->_error_msg = '短信发送达到限制';
- }
- //白名单
- $white_list = explode('|',$settingModel::getSetting('SMS_WHITE_LIST'));
- if (!$this->_is_error && !empty($this->_getLastTime())) { // 最后发送时间对比
- if (!in_array($this->_mobile,$white_list) && ($this->_now_time - $this->_last_time) < $this->_interval) {
- $this->_is_error = true;
- $this->_error_msg = '发送验证码操作太频繁,请稍后再试';
- }
- }
- if (!$this->_is_error && !$this->_isMobile()) {
- $this->_is_error = true;
- $this->_error_msg = '手机号错误';
- }
- }
- /**
- * 判断是否正确的手机号格式
- * @param string $mobile
- * @return bool
- */
- protected function _isMobile()
- {
- $validate = new ValidateExtend();
- $array = explode(',', $this->_mobile);
- foreach ( $array as $k => $v ) {
- if ( ! $validate->is($v, 'mobile') ) {
- unset($array[$k]);
- }
- }
- if ( empty($array) ) {
- return false;
- }
- $this->_mobile = join(',', $array);
- return true;
- }
- /**
- * 获取最后一次发送时间
- * @return string
- */
- protected function _getLastTime()
- {
- $this->_last_time = $this->_getValue('sendtime');
- return $this->_last_time;
- }
- /**
- * 获取redis中该手机号的某个key的值
- */
- protected function _getValue($key)
- {
- $response = null;
- if ($this->_redis->has($this->_key)) { // 存在这个key
- $data = $this->_redis->get($this->_key);
- $data = json_decode($data, true);
- if (isset($data[ $this->_mobile ])) { // 该号码已经有发送记录
- $response = $data[ $this->_mobile ][ $key ];
- }
- }
- return $response;
- }
- /**
- * 获取redis中保存的该手机号的验证码
- */
- protected function _getCode()
- {
- $this->_code = $this->_getValue('code');
- return $this->_code;
- }
- /**
- * 写入redis数据
- * @return null
- */
- protected function _setRedisData()
- {
- $this->_mobile = strval($this->_mobile);
- // 存在这个key
- if ($this->_redis->has($this->_key)) {
- $data = $this->_redis->get($this->_key);
- $data = json_decode($data, true);
- if (!isset($data[ $this->_mobile ])) {
- $data[ $this->_mobile ] = array(
- 'times' => 1,
- 'sendtime' => $this->_now_time,
- 'code' => $this->_code,
- );
- } else {
- $data[ $this->_mobile ]['times'] = ++$data[ $this->_mobile ]['times'];
- $data[ $this->_mobile ]['sendtime'] = $this->_now_time;
- $data[ $this->_mobile ]['code'] = $this->_code;
- }
- } else { // 不存在这个key,新建
- $data = array(
- $this->_mobile => array(
- 'times' => 1,
- 'sendtime' => time(),
- 'code' => $this->_code,
- ),
- ); //now-here
- }
- $this->_redis->set($this->_key, json_encode($data), 86400);
- }
- /**
- * 清除验证码,用于需要验证码失效的场景
- * @param string $mobile
- * @return void
- */
- public function clearVerifyCode($mobile){
- if ($this->_redis->has($this->_key)) {
- $data = $this->_redis->get($this->_key);
- $data = json_decode($data, true);
- if (isset($data[$mobile])) {
- $data[$mobile]['code'] = '';
- }
- $this->_redis->set($this->_key, json_encode($data), 86400);
- }
- }
- /**
- * 获取该手机号当天的发送次数
- * @return int
- */
- protected function _getTimes()
- {
- $this->_times = $this->_getValue('times');
- return $this->_times;
- }
- /**
- * 向短信服务商发送短信请求
- * @return string 请求结果
- */
- protected function _post()
- {
- $header[] = "Content-Type: application/json"; //定义content-type为xml,注意是数组
- $header[] = "charset=utf-8"; //定义content-type为xml,注意是数组
- $ch = curl_init();
- curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
- curl_setopt($ch, CURLOPT_URL, $this->_url);
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
- /* 设置超时时间*/
- curl_setopt($ch, CURLOPT_TIMEOUT, 300);
- curl_setopt($ch, CURLOPT_POST, 1);
- curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
- curl_setopt($ch, CURLOPT_POSTFIELDS, $this->_XML_string);
- $response = curl_exec($ch);
- if (curl_errno($ch)) {
- return 'other';
- }
- curl_close($ch);
- return json_decode($response,true);
- }
- /**
- * 生成验证码
- * @return null
- */
- protected function _makeCode()
- {
- $code = rand(111111, 999999);
- $this->_code = $code;
- }
- /**
- * 短信发送后的服务商返回的消息处理
- *
- * @param $result mix 服务商返回值
- */
- public function afterSend($result)
- {
- if(is_array($result)){
- if($result['status']=='success'){
- $this->_status = true;
- }
- else{
- $this->_is_error = true;
- $this->_error_msg = $result['respMsg'];
- }
- }
- else{
- $this->_is_error = true;
- $this->_error_msg = '未知错误';
- }
- }
- public function sendSms($mobile,$TemplateCode,$TemplateParam = array()) {
- $params = array ();
- $security = false;
- $accessKeyId = $this->_accessKeyId;
- $accessKeySecret = $this->_accessKeySecret;
- $params["PhoneNumbers"] = $mobile;
- if(in_array($TemplateCode, config('ALI_SMS_SIGN'))){
- $params["SignName"] = $this->_signName2;
- }else{
- $params["SignName"] = $this->_signName;
- }
- $params["TemplateCode"] = $TemplateCode;
- // fixme 可选: 设置模板参数, 假如模板中存在变量需要替换则为必填项
- if(!empty($TemplateParam) && is_array($TemplateParam)) {
- $params["TemplateParam"] = json_encode($TemplateParam, JSON_UNESCAPED_UNICODE);
- }
- $content = $this->request(
- $accessKeyId,
- $accessKeySecret,
- array_merge($params, array(
- "RegionId" => "cn-hangzhou",
- "Action" => "SendSms",
- "Version" => "2017-05-25",
- )),
- $security
- );
- return $content;
- }
- /**
- * 生成签名并发起请求
- *
- * @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
- * @param $method boolean 使用GET或POST方法请求,VPC仅支持POST
- * @return bool|\stdClass 返回API接口调用结果,当发生错误时返回false
- */
- public function request($accessKeyId, $accessKeySecret, $params, $security=false, $method='POST') {
- $apiParams = array_merge(array (
- "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 = "${method}&%2F&" . $this->encode(substr($sortedQueryStringTmp, 1));
- $sign = base64_encode(hash_hmac("sha1", $stringToSign, $accessKeySecret . "&",true));
- $signature = $this->encode($sign);
- $url = ($security ? 'https' : 'http')."://{$this->_domain}/";
- try {
- $content = $this->fetchContent($url, $method, "Signature={$signature}{$sortedQueryStringTmp}");
- return json_decode($content);
- } catch( \Exception $e) {
- return false;
- }
- }
- private function encode($str)
- {
- $res = urlencode($str);
- $res = preg_replace("/\+/", "%20", $res);
- $res = preg_replace("/\*/", "%2A", $res);
- $res = preg_replace("/%7E/", "~", $res);
- return $res;
- }
- private function fetchContent($url, $method, $body) {
- $ch = curl_init();
- if($method == 'POST') {
- curl_setopt($ch, CURLOPT_POST, 1);//post提交方式
- curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
- } else {
- $url .= '?'.$body;
- }
- curl_setopt($ch, CURLOPT_URL, $url);
- curl_setopt($ch, CURLOPT_TIMEOUT, 5);
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
- curl_setopt($ch, CURLOPT_HTTPHEADER, array(
- "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) {
- // 大多由设置等原因引起,一般无法保障后续逻辑正常执行,
- // 所以这里触发的是E_USER_ERROR,会终止脚本执行,无法被try...catch捕获,需要用户排查环境、网络等故障
- trigger_error("[CURL_" . curl_errno($ch) . "]: " . curl_error($ch), E_USER_ERROR);
- }
- curl_close($ch);
- return $rtn;
- }
- }
|