最新需要用到发送短信的功能,所以就在网上搜索一些写好的扩展。
扩展地址:
1 | https: //github.com/MissMyCat/aliyun-sms |
通过composer安装:
1 | composer require mrgoon/aliyun-sms dev-master |
在 config/app.php 中 providers 加入:
1 | Mrgoon\AliSms\ServiceProvider:: class , |
有需求的可以自行添加 aliases。
然后在控制台运行 :
1 | php artisan vendor:publish |
默认会在 config 目录下创建一个 aliyunsms.php 文件:
1 2 3 4 5 6 7 | <?php return [ 'access_key' => env( 'ALIYUN_SMS_AK' ), // accessKey 'access_secret' => env( 'ALIYUN_SMS_AS' ), // accessSecret 'sign_name' => env( 'ALIYUN_SMS_SIGN_NAME' ), // 签名 ]; |
然后在 .env 中配置相应参数:
1 2 3 | ALIYUN_SMS_AK= ALIYUN_SMS_AS= ALIYUN_SMS_SIGN_NAME= |
为了能够方便的发送短信,我们可以在 app 目录下,创建一个Services目录,并添加 AliyunSms.php 文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | <?php namespace App\Services; use Mrgoon\AliSms\AliSms; /** * 阿里云短信类 */ class AliyunSms { //验证码 const VERIFICATION_CODE = 'verification_code' ; //模板CODE public static $templateCodes = [ self::VERIFICATION_CODE => 'SMS_XXXXXXXXXX' , ]; /** * 发送短信 */ public static function sendSms( $mobile , $scene , $params = []) { if ( empty ( $mobile )) { throw new \Exception( '手机号不能为空' ); } if ( empty ( $scene )) { throw new \Exception( '场景不能为空' ); } if (!isset(self:: $templateCodes [ $scene ])) { throw new \Exception( '请配置场景的模板CODE' ); } $template_code = self:: $templateCodes [ $scene ]; try { $ali_sms = new AliSms(); $response = $ali_sms ->sendSms( $mobile , $template_code , $params ); if ( $response ->Code == 'OK' ) { return true; } throw new \Exception( $response ->Message); } catch (\Throwable $e ) { throw new \Exception( $e ->getMessage()); } } } |
为了能够记录每次短信发送的状态,我们可以创建一个 sms_logs 表。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | CREATE TABLE `sms_logs` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID' , `type` tinyint(1) NOT NULL DEFAULT '0' COMMENT '类型(0:短信验证码,1:语音验证码,2:短信消息通知)' , `mobile` varchar(16) NOT NULL DEFAULT '' COMMENT '手机号' , `code` varchar(12) NOT NULL DEFAULT '' COMMENT '验证码' , `checked` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否验证(0:未验证,1:已验证)' , `status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '状态(0:未发送,1:已发送,2:发送失败)' , `reason` varchar(255) NOT NULL DEFAULT '' COMMENT '失败原因' , `remark` varchar(255) NOT NULL DEFAULT '' COMMENT '备注' , `operator_id` int(11) NOT NULL DEFAULT '0' COMMENT '操作人ID' , `ip` varchar(16) NOT NULL DEFAULT '' COMMENT '操作IP' , `created` int(11) NOT NULL DEFAULT '0' COMMENT '创建时间' , `updated` int(11) NOT NULL DEFAULT '0' COMMENT '更新时间' , PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT= '短信表' ; |
然后针对该表,我们创建一个 SmsLog 模型来管理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 | <?php namespace App\Models; use App\Services\AliyunSms; class SmsLog extends Model { protected $fillable = [ 'type' , 'mobile' , 'code' , 'checked' , 'status' , 'reason' , 'remark' , 'operator_id' , 'ip' , ]; //类型(0:短信验证码,1:语音验证码,2:短信消息通知) const TYPE_CODE = 0; const TYPE_VOICE = 1; const TYPE_MESSAGE = 2; //是否验证(0:未验证,1:已验证) const CHECKED_UNVERIFIED = 0; const CHECKED_VERIFIED = 1; //状态(0:未发送,1:已发送,2:发送失败) const STATUS_NO_SEND = 0; const STATUS_SEND = 1; const STATUS_FAIL = 2; //短信发送间隔时间,默认60秒 const SEND_INTERVAL_TIME = 60; /** * 检测短信验证码 */ protected function checkCode( $mobile , $code ) { if (! $mobile ) { throw new \Exception( '手机号不能为空' ); } if (!checkMobile( $mobile )) { throw new \Exception( '手机号不正确' ); } if (! $code ) { throw new \Exception( '验证码不能为空' ); } $sms_log = $this ->where([ [ 'type' , self::TYPE_CODE], [ 'mobile' , $mobile ], [ 'status' , self::STATUS_SEND], [ 'checked' , self::CHECKED_UNVERIFIED], ])->orderBy( 'created' , 'desc' )->first(); if (! $sms_log ) { throw new \Exception( '验证码不存在,请重新获取' ); } if ( $code != $sms_log ->code) { throw new \Exception( '验证码错误' ); } $sms_log ->checked = self::CHECKED_VERIFIED; $sms_log ->save(); return true; } /** * 检测短信频率 */ protected function checkRate( $mobile ) { if (! $mobile ) { throw new \Exception( '手机号不能为空' ); } $sms_log = $this ->where([ [ 'mobile' , $mobile ], [ 'status' , self::STATUS_SEND], ])->orderBy( 'created' , 'desc' )->first(); $now = time(); if ( $sms_log ) { if (( $now - strtotime ( $sms_log ->created)) < self::SEND_INTERVAL_TIME) { throw new \Exception( '短信发送太频繁,请稍后再试' ); } } return true; } /** * 发送短信验证码 */ protected function sendVerifyCode( $mobile ) { self::checkRate( $mobile ); $code = mt_rand(1000, 9999); $sms_log = $this ->create([ 'type' => self::TYPE_CODE, 'mobile' => $mobile , 'code' => $code , 'checked' => self::CHECKED_UNVERIFIED, 'status' => self::STATUS_NO_SEND, 'ip' => getRealIp(), ]); try { AliyunSms::sendSms( $mobile , AliyunSms::VERIFICATION_CODE, [ 'code' => $code ]); $sms_log ->status = self::STATUS_SEND; $sms_log ->save(); return true; } catch (\Exception $e ) { $sms_log ->status = self::STATUS_FAIL; $sms_log ->reason = $e ->getMessage(); $sms_log ->save(); throw new \Exception( $e ->getMessage()); } } } |
这样,我们就可以在项目中通过 SmsLog::sendVerifyCode() 发送短信了。
getRealIp() 和 checkMobile() 方法为公共方法,存放在 app/Helpers 的 functions.php 中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | /** * 获取真实IP地址 */ function getRealIp() { $ip = false; if ( getenv ( "HTTP_CLIENT_IP" ) && strcasecmp ( getenv ( "HTTP_CLIENT_IP" ), "unknown" )) { $ip = getenv ( "HTTP_CLIENT_IP" ); } else if ( getenv ( "HTTP_X_FORWARDED_FOR" ) && strcasecmp ( getenv ( "HTTP_X_FORWARDED_FOR" ), "unknown" )) { $ips = explode ( ", " , getenv ( "HTTP_X_FORWARDED_FOR" )); if ( $ip ) { array_unshift ( $ips , $ip ); $ip = false; } $ipscount = count ( $ips ); for ( $i = 0; $i < $ipscount ; $i ++) { if (!preg_match( "/^(10|172\.16|192\.168)\./i" , $ips [ $i ])) { $ip = $ips [ $i ]; break ; } } } else if ( getenv ( "REMOTE_ADDR" ) && strcasecmp ( getenv ( "REMOTE_ADDR" ), "unknown" )) { $ip = getenv ( "REMOTE_ADDR" ); } else if (isset( $_SERVER [ 'REMOTE_ADDR' ]) && $_SERVER [ 'REMOTE_ADDR' ] && strcasecmp ( $_SERVER [ 'REMOTE_ADDR' ], "unknown" )) { $ip = $_SERVER [ 'REMOTE_ADDR' ]; } else { $ip = "unknown" ; } return isIp( $ip ) ? $ip : "unknown" ; } /** * 检查是否是合法的IP */ function isIp( $ip ) { if (preg_match( '/^((\d|[1-9]\d|2[0-4]\d|25[0-5]|1\d\d)(?:\.(\d|[1-9]\d|2[0-4]\d|25[0-5]|1\d\d)){3})$/' , $ip )) { return true; } else { return false; } } /** * 验证手机号 */ function checkMobile( $mobile ) { return preg_match( '/^((13[0-9])|(14[5,7])|(15[0-3,5-9])|(17[0,3,5-8])|(18[0-9])|166|198|199|(147))\d{8}$/i' , $mobile ); } |
版权声明:博主文章,可以不经博主允许随意转载,随意修改,知识是用来传播的。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 字符编码:从基础到乱码解决