TP5 封装多业务的发送短信功能(包括国际短信)
1、準(zhǔn)備工作
1.1、準(zhǔn)備依賴包
- 這個(gè)網(wǎng)站提供的都是PHP包,挺有用的 https://packagist.org/
- 另外還可以在GitHub上面下載 https://github.com/
1、短信方面 我這里用到 packagist 里面的 overtrue/easy-sms
下載依賴包之前記得要看下依賴的PHP版本、短信平臺(tái)等。
我們可以看到這個(gè)依賴包提供的平臺(tái)有以下:
2、Redis方面 我用到了Predis的包
另外他包里面提供了使用方法和類型,需要去看下,不過(guò)本人進(jìn)行了代碼封裝。
安裝這些包的話直接打開(kāi)cmd命令行輸入 composer require XXX 即可,
XXX是對(duì)應(yīng)包名,XXX后面如果加 **:1.***就相當(dāng)于版本號(hào),包里面會(huì)介紹相關(guān)的操作。
##2、代碼封裝
#####2.1、封裝的文件
- 包括兩個(gè) 服務(wù)類 和 config.php ,用于封裝不同業(yè)務(wù)類型的存儲(chǔ)和發(fā)送的方法,如下圖
2.2、配置文件
- config.php 文件,用于保存短信配置和白名單等,內(nèi)容如下
其中 Env 是對(duì)應(yīng)的配置文件,你也可以直接在第二個(gè)參數(shù)填寫默認(rèn)的配置即可。
2.3、Redis服務(wù)類
<?phpnamespace app\common\service;use Predis\Client;class RedisService {//Redis保存的key//短信部分const SU_SMS_LOGIN = 'sms::login::'; //短信驗(yàn)證碼|登錄和找回密碼(后面跟 國(guó)際區(qū)號(hào)-用戶手機(jī)號(hào))const SU_SMS_RESET_PWD = 'sms::reset::pwd::'; //短信驗(yàn)證碼|重置賬號(hào)密碼(后面跟 國(guó)際區(qū)號(hào)-用戶手機(jī)號(hào))const SU_SMS_RESET_MOBILE = 'sms::reset::mobile::'; //短信驗(yàn)證碼|重置手機(jī)號(hào)碼(后面跟 國(guó)際區(qū)號(hào)-用戶手機(jī)號(hào))const SU_SMS_CREATE_ACCOUNT_MOBILE = 'sms::create::account::mobile::'; //創(chuàng)建子賬號(hào)|修改子賬號(hào)手機(jī)號(hào)(后面跟 國(guó)際區(qū)號(hào)-用戶手機(jī)號(hào))const SU_SMS_OTHER = 'sms::other::'; //短信驗(yàn)證碼|其他情況(后面跟 國(guó)際區(qū)號(hào)-用戶手機(jī)號(hào))const SU_SMS_NUM = 'sms::num::'; //手機(jī)短信發(fā)送次數(shù)(后面跟 國(guó)際區(qū)號(hào)-用戶手機(jī)號(hào))private static $prefix = '';private static $client;/*** 單例模式獲取redis連接實(shí)例* @return Client*/public static function connect(){if (!self::$client) {self::$prefix = config('redis_prefix');$config = ['scheme' => 'tcp','host' => config('redis_host'),'port' => config('redis_port'),];//沒(méi)有配置密碼時(shí),不傳入密碼項(xiàng)參數(shù)if (config('redis_password')) $config['password'] = config('redis_password');self::$client = new Client($config, ['prefix' => self::$prefix]);}return self::$client;}/*** 校驗(yàn)短信驗(yàn)證碼* @param string $areaCode 手機(jī)國(guó)際區(qū)號(hào)* @param string $mobile 手機(jī)號(hào)* @param string $smsCode 驗(yàn)證碼* @param string $prefix 根據(jù)業(yè)務(wù)區(qū)分的短信前綴* @param bool $isThrowException 是否拋出異常* @param bool $isDel 檢查完后是否刪除該緩存* @return array*/public static function checkSmsCode(string $areaCode, string $mobile, string $smsCode, string $prefix = self::SU_SMS_LOGIN, bool $isThrowException = true, bool $isDel = true){$res = [true, '短信驗(yàn)證碼正確!'];if (!self::connect()->exists($prefix .$areaCode . '-' . $mobile)) {$isThrowException ? throwResult('手機(jī)驗(yàn)證碼失效') : $res = [false, '手機(jī)驗(yàn)證碼失效'];} else {if (!hash_equals($smsCode, self::connect()->get($prefix . $areaCode . '-' . $mobile)))$isThrowException ? throwResult('手機(jī)驗(yàn)證碼不正確') : $res = [false, '手機(jī)驗(yàn)證碼不正確'];}if ($isDel) self::connect()->del($prefix . $mobile);return $res;}最上面定義的常量都是用于定義業(yè)務(wù)類型,后面跟著手機(jī)號(hào)
throwResult 是封裝好的拋出異常的方法,使用自己封裝的即可。
connect 方法是用于實(shí)例化,每次用redis時(shí)候直接RedisService::connect();
checkSmsCode 方法是用于檢驗(yàn)驗(yàn)證碼是否正確的,其中需要傳業(yè)務(wù)類型前綴。
2.4、發(fā)送短信服務(wù)類
<?phpnamespace app\common\service;use Overtrue\EasySms\EasySms; use Overtrue\EasySms\Exceptions\NoGatewayAvailableException;class SmsService {/*** 發(fā)送通用短信驗(yàn)證碼* @param string $areaCode 手機(jī)國(guó)際區(qū)號(hào)* @param string $mobile 手機(jī)號(hào)* @param int $smsUseType 業(yè)務(wù)類型:0=注冊(cè)登錄,1=更換密碼,2=修改手機(jī)號(hào),3=創(chuàng)建子賬號(hào),10=其他* @param int $userId 用戶ID* @return int* @throws \Overtrue\EasySms\Exceptions\InvalidArgumentException* @throws \app\common\exception\WorkException*/public static function sendSmsCode(string $areaCode,string $mobile, int $smsUseType = 0, int $userId = 0){$redis = RedisService::connect();$countKey = RedisService::SU_SMS_NUM . $mobile; //記錄該手機(jī)發(fā)送的次數(shù)//驗(yàn)證碼業(yè)務(wù)類型switch ($smsUseType) {case 0://注冊(cè)登錄$smsKey = RedisService::SU_SMS_LOGIN . $areaCode . '-' . $mobile;break;case 1://重置密碼$smsKey = RedisService::SU_SMS_RESET_PWD . $areaCode . '-' . $mobile;break;case 2://修改手機(jī)號(hào)$smsKey = RedisService::SU_SMS_RESET_MOBILE . $areaCode . '-' . $mobile;break;case 3://創(chuàng)建子賬號(hào)$smsKey = RedisService::SU_SMS_CREATE_ACCOUNT_MOBILE . $areaCode . '-' . $mobile;break;case 10://其他$smsKey = RedisService::SU_SMS_OTHER . $areaCode . '-' . $mobile;break;default://通用$smsKey = RedisService::SU_SMS_OTHER . $areaCode . '-' . $mobile;break;}//白名單可以無(wú)限制使用發(fā)送次數(shù)if (!in_array($mobile, config('sms_limit.white_list'))) {//防止惡意發(fā)送,每小時(shí)限制5次$count = $redis->get($countKey);if ($count && $count >= config('sms_limit.send_total')) {throwResult('超過(guò)發(fā)送次數(shù)限制,請(qǐng)稍后再試');}}//檢查該手機(jī)號(hào)一段時(shí)間內(nèi)已發(fā)送的次數(shù)$redisIncr = $redis->incr($countKey);if ($redisIncr == 1) $redis->expire($countKey, config('sms_limit.save_time')); //設(shè)置保存時(shí)間 默認(rèn)一小時(shí)//查找如果存在相同鍵名的短信碼則更新采用舊的編碼$smsCode = config('app_debug') ? 8888 : ($redis->get($smsKey) ?: mt_rand(1000, 9999));//保存驗(yàn)證碼并設(shè)置15分鐘有效時(shí)間$redis->set($smsKey, $smsCode, 'EX', config('sms_limit.expires')); //todo 短信驗(yàn)證碼過(guò)期時(shí)間//發(fā)送短信并保存記錄if (!config('app_debug')) self::send($areaCode,$mobile, ['content' => $smsContent], $smsCode, $smsUseType, $userId);return $smsCode;}/*** 通用短信發(fā)送方法* @param string $areaCode 手機(jī)國(guó)際區(qū)號(hào)* @param string $mobile 手機(jī)號(hào)碼* @param string $content 發(fā)送內(nèi)容* @param string $smsCode 手機(jī)驗(yàn)證碼(沒(méi)有則不需要傳參)* @param int $smsUseType 0=注冊(cè)登錄,1=更換密碼,2=修改手機(jī)號(hào),3=創(chuàng)建子賬號(hào),10=其他* @param int $userId 用戶ID 默認(rèn)為0(用戶不存在時(shí)候不需要傳參)* @return bool* @throws \Overtrue\EasySms\Exceptions\InvalidArgumentException*/public static function send(string $areaCode, string $mobile, string $content, string $smsCode = '', int $smsUseType = 0, int $userId = 0){//發(fā)送短信的內(nèi)容$isSuccess = 1;try {$mobile = new PhoneNumber($mobile, $areaCode);$option = ['content' => $content, //文字內(nèi)容,使用在像云片類似的以文字內(nèi)容發(fā)送的平臺(tái)'template' => 'SMS_001', // 模板 ID,使用在以模板ID來(lái)發(fā)送短信的平臺(tái)'data' => //模板變量,使用在以模板ID來(lái)發(fā)送短信的平臺(tái)['code' => 6379],];$easySms = new EasySms(config('easy_sms'));$easySms->send($mobile, $option);} catch (NoGatewayAvailableException $e) {$isSuccess = 3;}//短信驗(yàn)證碼記錄成功與失敗到表 // SmsModel::createOne($mobile, $option['content'], $isSuccess, $smsUseType, $smsCode, $userId);return $isSuccess == 1 ? true : false;} }我封裝的時(shí)候加了一些附加條件:
-
業(yè)務(wù)類型 我們根據(jù)不同的業(yè)務(wù)類型進(jìn)行不同的 鍵值 保存和操作。
-
限制次數(shù) 白名單用戶可以無(wú)限次發(fā)送,而普通用戶限制每小時(shí)最多發(fā)送多少次,主要通過(guò) SU_SMS_NUM 這個(gè)redis 鍵值去判斷,這個(gè)鍵值需要根據(jù)具體業(yè)務(wù)設(shè)置個(gè)時(shí)間,比如1小時(shí)。
-
驗(yàn)證碼時(shí)長(zhǎng) 驗(yàn)證碼需要存入redis中并設(shè)置一個(gè)時(shí)間,保證驗(yàn)證碼的有效時(shí)長(zhǎng)。
-
穩(wěn)定驗(yàn)證碼 每次發(fā)送驗(yàn)證碼之前需要去查看是否存在 有效驗(yàn)證碼 ,有的話則直接發(fā)送該驗(yàn)證碼。
3、實(shí)現(xiàn)過(guò)程
3.1、發(fā)送短信
/*** @ApiTitle (發(fā)送短信驗(yàn)證碼)* @ApiSummary (用于發(fā)送短信驗(yàn)證碼)* @ApiMethod (POST)* @ApiRoute (/api/User/sendSmsByPhone)* @ApiHeaders (name=Authorization, type=string, required=false, description="用戶Token,其中修改手機(jī)號(hào)需要傳")* @ApiParams (name="mobile", type="string", required=true, description="手機(jī)號(hào)")* @ApiParams (name="area_code", type="string", required=true, description="國(guó)際區(qū)號(hào)")* @ApiParams (name="sms_type", type="string", required=false, description="業(yè)務(wù)類型:0=注冊(cè)登錄,1=更換密碼,2=修改手機(jī)號(hào),3=創(chuàng)建子賬號(hào)和修改子賬號(hào)手機(jī)號(hào),10=其他,默認(rèn)為0")*/ public function sendSmsByPhone() {$areaCode = input('area_code'); //國(guó)際區(qū)號(hào)$mobile = input('mobile'); //手機(jī)號(hào)$smsType = intval(input('sms_type')) ?? 0; //短信類型//驗(yàn)證數(shù)據(jù)$this->validate(['mobile' => $mobile,'area_code' => $areaCode,'sms_type' => $smsType,], 'UserValidate.send_sms_by_phone');//業(yè)務(wù)類型判斷if ($smsType == 1) { //重置、找回密碼 // $user = $this->auth->getUser(); // if (!$user) $this->error(MSG_NEED_LOGIN);} elseif ($smsType == 2) { //修改手機(jī)號(hào)需要登錄和驗(yàn)證手機(jī)號(hào)$user = $this->auth->getUser();if (!$user) $this->error(MSG_NEED_LOGIN);$oldMobile = $user->toArray()['mobile'];$oldAreaCode = $user->toArray()['area_code'];if ($areaCode . $mobile == $oldAreaCode . $oldMobile) $this->error('修改的手機(jī)號(hào)和原手機(jī)號(hào)相同!');} elseif ($smsType == 3) { //創(chuàng)建子賬號(hào)//判斷賬戶是否存在$id = db('user')->where('mobile', $mobile)->where('area_code', $areaCode)->value('id');if ($id) {$this->error('該賬戶已存在');}}//驗(yàn)證該手機(jī)號(hào)是否存在,存在則驗(yàn)證狀態(tài) 狀態(tài):0=正常,1=檢測(cè)中,2=已凍結(jié),3=封號(hào)$user = (new UserModel)->where('mobile', $mobile)->field(['id', 'status'])->find();$userid = 0;if ($user) {if ($user['status'] == 2) $this->error(MSG_USER_STATUS_FREEZE);if ($user['status'] == 3) $this->error(MSG_USER_STATUS_BAN);$userid = $user['id'];}//判斷業(yè)務(wù)類型$smsCode = SmsService::sendSmsCode($areaCode, $mobile, $smsType, $userid);//調(diào)試模式時(shí),接口返回短信驗(yàn)證碼的值$res['mobile'] = $mobile;$res['area_code'] = $areaCode;if (config('app_debug')) $res['_sms_code'] = $smsCode;$this->success(MSG_OK, $res, '手機(jī)驗(yàn)證碼發(fā)送成功'); }3.2、驗(yàn)證短信并登陸
/*** @ApiTitle (會(huì)員手機(jī)號(hào)注冊(cè)和登錄)* @ApiSummary (用戶用于手機(jī)號(hào)注冊(cè)和登錄平臺(tái))* @ApiMethod (POST)* @ApiRoute (/api/User/mobileLogin)* @ApiParams (name="area_code", type="string", required=true, description="手機(jī)國(guó)際區(qū)號(hào)")* @ApiParams (name="mobile", type="string", required=true, description="手機(jī)號(hào)")* @ApiParams (name="sms_code", type="string", required=true, description="手機(jī)驗(yàn)證碼")* @ApiParams (name="type", type="int", required=true, description="類型:0=未知設(shè)備,1=安卓APP,2=IOSAPP,3=微信小程序,4=H5頁(yè)面,5=PC端")*/public function mobileLogin(){$areaCode = input('area_code'); //手機(jī)國(guó)際區(qū)號(hào)$mobile = input('mobile'); //手機(jī)號(hào)$smsCode = input('sms_code'); //手機(jī)驗(yàn)證碼$type = input('type') ?? 0; //類型:0=未知設(shè)備,1=安卓APP,2=IOSAPP,3=微信小程序,4=H5頁(yè)面,5=PC端//驗(yàn)證數(shù)據(jù)$this->validate(['mobile' => $mobile,'sms_code' => $smsCode,'area_code' => $areaCode,], 'UserValidate.mobile_login');//發(fā)送短信檢測(cè)RedisService::checkSmsCode(areaCode,$mobile, $smsCode, RedisService::SU_SMS_LOGIN);$userId = (new UserModel)->where('mobile', $mobile)->where('area_code', $areaCode)->value('id');$ip = request()->ip();$time = time();$redis = new Redis();if (!$userId) {//如果不存在則直接注冊(cè)$data = ['area_code' => $areaCode, //手機(jī)國(guó)際區(qū)號(hào)'mobile' => $mobile, //手機(jī)號(hào)'score' => 0, //積分(考慮第一次注冊(cè)是否有積分)'logintime' => $time, //登錄時(shí)間'loginip' => $ip, //登錄IP'prevtime' => $time, //上次登錄時(shí)間'status' => 0 //狀態(tài):0=正常,1=檢測(cè)中,2=已凍結(jié),3=封號(hào)];//賬號(hào)注冊(cè)時(shí)需要開(kāi)啟事務(wù),避免出現(xiàn)垃圾數(shù)據(jù)Db::startTrans();try {//插入用戶表$userId = (new UserModel)->insertGetId($data);$rand1 = rand(10000000, 99999999);$rand2 = rand(10, 99);$username = 'SuLink-' . substr($rand1 . $userId . $rand2, -11); //取10位數(shù)//再設(shè)置用戶昵稱(new UserModel)->where('id', $userId)->update(['nickname' => $username, //昵稱'username' => $username, //用戶名'avatar' => letter_avatar($username)]);//插入登錄記錄(new UserLoginLogModel)->insert(['user_id' => $userId,'type' => $type,'user_agent' => \request()->header('User-Agent'),'login_ip' => $ip,]);//新增用戶設(shè)置表(new UserSettingModel)->insert(['user_id' => $userId]);//設(shè)置Token$token = Random::uuid();$redis->set($token, $userId, config('token.keep_time'));Db::commit();} catch (Exception $e) {Db::rollback();$this->error(MSG_ERR, null, $e->getMessage());}} else {$token = $this->loginSuccess($userId, $type, $time);}$this->success(MSG_OK, ['id' => $userId, 'mobile' => $mobile, 'token' => $token], '登錄成功');}#####3.3、總結(jié)
- 從上面我們可以提取出主要的部分,調(diào)用時(shí)候主要使用兩個(gè)部分就行了
1、發(fā)送驗(yàn)證碼只需要簡(jiǎn)單的調(diào)用短信服務(wù)類方法 sendSmsCode
//判斷業(yè)務(wù)類型 $smsCode = SmsService::sendSmsCode($mobile, $smsType, $userid);2、驗(yàn)證驗(yàn)證碼只需要簡(jiǎn)單的調(diào)用Redis服務(wù)類方法 checkSmsCode
//發(fā)送短信檢測(cè) RedisService::checkSmsCode($mobile, $smsCode, RedisService::SU_SMS_LOGIN);如果覺(jué)得那部分不清楚的可以評(píng)論發(fā)出提問(wèn),或者哪里寫的不好的也可以提出來(lái)。
謝謝大家的觀賞,樓上的方法都是本人親自封裝。
總結(jié)
以上是生活随笔為你收集整理的TP5 封装多业务的发送短信功能(包括国际短信)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Web端a标签跳转地图等链接(收藏)
- 下一篇: TP5 使用IN查询时如何限制条数