_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; } }