[fastadmin] 第四十五篇 FastAdmin 集成阿里云新融合认证短信产品发送验证码
FastAdmin 集成阿里云新融合认证短信产品发送验证码
阿里云地址: dypns.console.aliyun.com/smsConnec...
前言
在现代 Web 应用开发中,短信验证码是用户身份验证的重要手段。本文将详细介绍如何在 FastAdmin 框架中集成阿里云新融合认证短信产品,实现验证码发送功能。
阿里云新融合认证短信产品相比传统短信服务,具有更高的到达率、更快的发送速度以及更好的安全性,是企业级应用的理想选择。
环境要求
- FastAdmin 框架
- PHP >= 5.5
- Composer 包管理器
- 阿里云账号及短信服务权限
第一步:安装阿里云 SDK 依赖
1.1 使用 Composer 安装
在 FastAdmin 项目根目录下执行以下命令:
composer require alibabacloud/dypnsapi-20170525 alibabacloud/darabonba-openapi alibabacloud/tea-console alibabacloud/tea-utils alibabacloud/credentials
1.2 解决权限问题
如果遇到权限问题(如 Permission denied
),需要以管理员身份运行命令行:
Windows 系统:
- 按
Win + R
,输入cmd
- 按
Ctrl + Shift + Enter
以管理员身份运行 - 切换到项目目录后重新执行安装命令
1.3 手动配置方式
如果 Composer 安装遇到问题,也可以手动编辑 composer.json
文件:
{
"require": {
"alibabacloud/dypnsapi-20170525": "^1.2.0",
"alibabacloud/darabonba-openapi": "^0.2.15",
"alibabacloud/tea-console": "^0.1.3",
"alibabacloud/tea-utils": "^0.2.21",
"alibabacloud/credentials": "^1.2.3"
}
}
然后执行:
composer update
第二步:配置阿里云参数
2.1 修改配置文件
在 application/extra/site.php
中添加阿里云配置:
return [
// ... 其他配置 ...
// 阿里云短信配置
'aliyun_access_key_id' => 'your_access_key_id',
'aliyun_access_key_secret' => 'your_access_key_secret',
'aliyun_sms_sign_name' => '速通互联验证平台',
'aliyun_sms_template_code' => '100001',
];
2.2 获取阿里云配置信息
AccessKey ID 和 AccessKey Secret:
- 登录阿里云控制台
- 进入”访问控制” > “用户管理”
- 创建用户并授权短信服务权限
- 获取 AccessKey 信息
短信签名:
- 在短信服务控制台申请签名
- 等待审核通过后使用
短信模板:
- 创建验证码类型模板
- 模板示例:
您的验证码是${code},${min}分钟内有效
第三步:创建阿里云短信服务类
在 application/common/library/
目录下创建 AliyunSms.php
:
<?php
namespace app\common\library;
// 确保 autoload 文件被正确加载
if (!class_exists('AlibabaCloud\SDK\Dypnsapi\V20170525\Dypnsapi')) {
$autoloadPath = ROOT_PATH . 'vendor/autoload.php';
if (file_exists($autoloadPath)) {
require_once $autoloadPath;
} else {
throw new \Exception('Composer autoload file not found. Please run: composer install');
}
}
use AlibabaCloud\SDK\Dypnsapi\V20170525\Dypnsapi;
use AlibabaCloud\Credentials\Credential;
use AlibabaCloud\Tea\Utils\Utils;
use \Exception;
use AlibabaCloud\Tea\Exception\TeaError;
use Darabonba\OpenApi\Models\Config;
use AlibabaCloud\SDK\Dypnsapi\V20170525\Models\SendSmsVerifyCodeRequest;
use AlibabaCloud\Tea\Utils\Utils\RuntimeOptions;
class AliyunSms
{
private $accessKeyId;
private $accessKeySecret;
private $signName;
private $templateCode;
public function __construct()
{
// 检查必要的类是否存在
if (!class_exists('AlibabaCloud\Credentials\Credential')) {
throw new \Exception('阿里云 SDK 未正确安装,请检查 Composer 依赖');
}
// 从配置文件中读取参数
$this->accessKeyId = config('site.aliyun_access_key_id');
$this->accessKeySecret = config('site.aliyun_access_key_secret');
$this->signName = config('site.aliyun_sms_sign_name');
$this->templateCode = config('site.aliyun_sms_template_code');
// 验证配置参数
if (empty($this->accessKeyId) || empty($this->accessKeySecret)) {
throw new \Exception('阿里云 AccessKey 配置不完整');
}
}
/**
* 创建阿里云客户端
* @return Dypnsapi
*/
private function createClient()
{
try {
$credential = new Credential([
'accessKeyId' => $this->accessKeyId,
'accessKeySecret' => $this->accessKeySecret,
]);
$config = new Config([
"credential" => $credential
]);
$config->endpoint = "dypnsapi.aliyuncs.com";
return new Dypnsapi($config);
} catch (\Exception $e) {
throw new \Exception('创建阿里云客户端失败:' . $e->getMessage());
}
}
/**
* 发送短信验证码
* @param string $mobile 手机号
* @param string $code 验证码
* @param string $templateParam 模板参数(可选)
* @return array
*/
public function sendVerifyCode($mobile, $code, $templateParam = null)
{
try {
$client = $this->createClient();
// 默认模板参数
if (is_null($templateParam)) {
$templateParam = json_encode([
"code" => $code,
"min" => 10
]);
}
$sendSmsVerifyCodeRequest = new SendSmsVerifyCodeRequest([
"countryCode" => "86",
"phoneNumber" => $mobile,
"signName" => $this->signName,
"templateCode" => $this->templateCode,
"templateParam" => $templateParam,
"codeLength" => strlen($code),
"validTime" => 10, // 验证码有效期10分钟
"duplicatePolicy" => 1,
"codeType" => 1,
"returnVerifyCode" => false
]);
$runtime = new RuntimeOptions([]);
$resp = $client->sendSmsVerifyCodeWithOptions($sendSmsVerifyCodeRequest, $runtime);
// 解析响应
$responseBody = $resp->body;
if ($responseBody->code === 'OK') {
return [
'code' => 1,
'msg' => '短信发送成功',
'data' => [
'bizId' => $responseBody->bizId ?? '',
'requestId' => $responseBody->requestId ?? ''
]
];
} else {
return [
'code' => 0,
'msg' => '短信发送失败:' . ($responseBody->message ?? '未知错误'),
'data' => []
];
}
} catch (Exception $error) {
if ($error instanceof TeaError) {
return [
'code' => 0,
'msg' => '短信发送异常:' . $error->message,
'data' => []
];
} else {
return [
'code' => 0,
'msg' => '短信发送异常:' . $error->getMessage(),
'data' => []
];
}
}
}
/**
* 发送自定义短信
* @param string $mobile 手机号
* @param array $templateParams 模板参数
* @param string $templateCode 模板代码(可选)
* @return array
*/
public function sendCustomSms($mobile, $templateParams, $templateCode = null)
{
$templateCode = $templateCode ?: $this->templateCode;
$templateParam = json_encode($templateParams);
// 如果是验证码类型,提取验证码
$code = isset($templateParams['code']) ? $templateParams['code'] : rand(1000, 9999);
return $this->sendVerifyCode($mobile, $code, $templateParam);
}
}
第四步:修改控制器方法
4.1 修改 Index 控制器的 sendcode 方法
/**
* 发送短信验证码
*/
public function sendcode()
{
$mobile = $this->request->post('phone');
$rule = [
'mobile' => 'require|length:11,11|regex:/^1[3-9]\d{9}$/',
];
$data = [
'mobile' => $mobile,
];
$validate = new Validate($rule, [], ['mobile' => __('Phone')]);
$result = $validate->check($data);
if (!$result) {
$this->error($validate->getError());
}
// 判断手机号是否已经注册
$user = \app\admin\model\Admin::where("mobile", $mobile)->find();
if (!$user) {
$this->error("手机号未注册");
}
// 频率限制:同一手机号60秒内只能发送一次
$lastSendTime = cache('sms_send_time_' . $mobile);
if ($lastSendTime && (time() - $lastSendTime) < 60) {
$this->error('发送频率过快,请稍后再试');
}
// 生成随机的四位验证码
$code = rand(1000, 9999);
try {
// 使用阿里云融合短信发送验证码
$aliyunSms = new \app\common\library\AliyunSms();
$result = $aliyunSms->sendVerifyCode($mobile, $code);
if ($result['code'] === 1) {
// 短信发送成功,将验证码存储到缓存
\app\common\library\Sms::send($mobile, $code, 'usuallycode');
// 记录发送时间
cache('sms_send_time_' . $mobile, time(), 60);
$this->success(__('Send code successful'));
} else {
$this->error($result['msg']);
}
} catch (\Exception $e) {
// 异常处理
\think\Log::error('短信发送异常:' . $e->getMessage());
$this->error('短信发送失败:' . $e->getMessage());
}
}
4.2 验证码验证逻辑
在登录方法中验证短信验证码:
public function loginmobile()
{
// ... 前面的代码保持不变 ...
if ($this->request->isPost()) {
$mobile = $this->request->post('phone');
$code = $this->request->post('code');
$keeplogin = $this->request->post('keeplogin');
$token = $this->request->post('__token__');
// 验证表单数据
$rule = [
'phone' => 'require|length:11,11|regex:/^1[3-9]\d{9}$/',
'code' => 'require|length:4,4|number',
'__token__' => 'require|token',
];
$data = [
'phone' => $mobile,
'code' => $code,
'__token__' => $token,
];
$validate = new Validate($rule, [], ['phone' => __('Phone'), 'code' => __('Code')]);
$result = $validate->check($data);
if (!$result) {
$this->error($validate->getError(), '', ['token' => $this->request->token()]);
}
AdminLog::setTitle(__('MobileLogin'));
// 验证手机短信验证码
$ret = \app\common\library\Sms::check($mobile, $code, "usuallycode");
if ($ret) {
// 验证码正确,通过手机号查询用户
$user = \app\admin\model\Admin::where("mobile", $mobile)->find();
if ($user) {
$username = $user->username;
$result = $this->auth->loginByMobileCode($mobile, $keeplogin ? 86400 : 0);
if ($result === true) {
Hook::listen("admin_login_after", $this->request);
$this->success(__('Login successful'), $url, [
'url' => $url,
'id' => $this->auth->id,
'username' => $username,
'avatar' => $this->auth->avatar
]);
} else {
$msg = $this->auth->getError();
$msg = $msg ? $msg : __('Login failed');
$this->error($msg, '', ['token' => $this->request->token()]);
}
} else {
$this->error("手机号未注册", '', ['token' => $this->request->token()]);
}
} else {
$this->error("短信验证码错误", '', ['token' => $this->request->token()]);
}
}
// ... 后面的代码保持不变 ...
}
第五步:前端集成
5.1 修改登录页面
在 application/admin/view/index/loginmobile.html
中添加发送验证码按钮:
<div class="form-group">
<label class="control-label">手机号</label>
<input type="text" class="form-control" name="phone" placeholder="请输入手机号" />
</div>
<div class="form-group">
<label class="control-label">验证码</label>
<div class="input-group">
<input type="text" class="form-control" name="code" placeholder="请输入验证码" maxlength="4" />
<span class="input-group-btn">
<button class="btn btn-info btn-send-code" type="button">发送验证码</button>
</span>
</div>
</div>
5.2 JavaScript 处理
$(document).ready(function() {
// 发送验证码
$('.btn-send-code').click(function() {
var $btn = $(this);
var phone = $('input[name="phone"]').val();
if (!phone || !/^1[3-9]\d{9}$/.test(phone)) {
Toastr.error('请输入正确的手机号');
return;
}
// 防止重复点击
if ($btn.hasClass('disabled')) {
return;
}
$.ajax({
url: 'index/sendcode',
type: 'POST',
data: {
phone: phone
},
beforeSend: function() {
$btn.addClass('disabled').text('发送中...');
},
success: function(data) {
if (data.code === 1) {
Toastr.success('验证码发送成功');
// 开始倒计时
startCountdown($btn, 60);
} else {
Toastr.error(data.msg || '发送失败');
$btn.removeClass('disabled').text('发送验证码');
}
},
error: function() {
Toastr.error('网络错误,请重试');
$btn.removeClass('disabled').text('发送验证码');
}
});
});
// 倒计时函数
function startCountdown($btn, seconds) {
var timer = setInterval(function() {
seconds--;
$btn.text(seconds + 's后重发');
if (seconds <= 0) {
clearInterval(timer);
$btn.removeClass('disabled').text('发送验证码');
}
}, 1000);
}
});
第六步:安全优化
6.1 添加频率限制
/**
* 检查发送频率
* @param string $mobile 手机号
* @return bool
*/
private function checkSendFrequency($mobile)
{
// 检查1分钟内发送次数
$minuteKey = 'sms_minute_' . $mobile . '_' . date('YmdHi');
$minuteCount = cache($minuteKey) ?: 0;
if ($minuteCount >= 1) {
return false;
}
// 检查1小时内发送次数
$hourKey = 'sms_hour_' . $mobile . '_' . date('YmdH');
$hourCount = cache($hourKey) ?: 0;
if ($hourCount >= 5) {
return false;
}
// 检查1天内发送次数
$dayKey = 'sms_day_' . $mobile . '_' . date('Ymd');
$dayCount = cache($dayKey) ?: 0;
if ($dayCount >= 10) {
return false;
}
return true;
}
/**
* 记录发送次数
* @param string $mobile 手机号
*/
private function recordSendCount($mobile)
{
// 记录分钟级别
$minuteKey = 'sms_minute_' . $mobile . '_' . date('YmdHi');
cache($minuteKey, (cache($minuteKey) ?: 0) + 1, 60);
// 记录小时级别
$hourKey = 'sms_hour_' . $mobile . '_' . date('YmdH');
cache($hourKey, (cache($hourKey) ?: 0) + 1, 3600);
// 记录天级别
$dayKey = 'sms_day_' . $mobile . '_' . date('Ymd');
cache($dayKey, (cache($dayKey) ?: 0) + 1, 86400);
}
6.2 添加 IP 限制
/**
* 检查 IP 发送频率
* @param string $ip IP地址
* @return bool
*/
private function checkIpFrequency($ip)
{
$ipKey = 'sms_ip_' . $ip . '_' . date('YmdH');
$ipCount = cache($ipKey) ?: 0;
// 每小时每个IP最多发送20条
return $ipCount < 20;
}
第七步:日志记录
7.1 添加短信发送日志
/**
* 记录短信发送日志
* @param string $mobile 手机号
* @param string $code 验证码
* @param array $result 发送结果
*/
private function logSmsRecord($mobile, $code, $result)
{
$logData = [
'mobile' => $mobile,
'code' => $code,
'result' => $result,
'ip' => request()->ip(),
'user_agent' => request()->header('user-agent'),
'created_time' => time()
];
// 记录到日志文件
\think\Log::info('SMS发送记录', $logData);
// 也可以记录到数据库
// Db::name('sms_log')->insert($logData);
}
使用示例
基本使用
use app\common\library\AliyunSms;
$aliyunSms = new AliyunSms();
$result = $aliyunSms->sendVerifyCode('13800138000', '1234');
if ($result['code'] === 1) {
echo '发送成功';
} else {
echo '发送失败:' . $result['msg'];
}
自定义模板参数
$templateParams = [
'code' => '1234',
'min' => 5,
'product' => '您的应用名称'
];
$result = $aliyunSms->sendCustomSms('13800138000', $templateParams);
常见问题及解决方案
1. Composer 权限问题
问题:Permission denied
错误
解决:以管理员身份运行命令行
2. 类找不到错误
问题:Class not found
错误
解决:检查 autoload 文件是否正确加载
3. 短信发送失败
问题:返回错误码
解决:
- 检查 AccessKey 配置
- 确认短信签名和模板已审核通过
- 查看阿里云控制台错误日志
4. 验证码接收延迟
问题:验证码到达较慢
解决:
- 检查网络连接
- 确认手机号格式正确
- 联系阿里云技术支持
总结
通过以上步骤,我们成功在 FastAdmin 中集成了阿里云新融合认证短信产品。这个解决方案具有以下优势:
- 高可靠性:使用阿里云官方 SDK,稳定可靠
- 易于维护:代码结构清晰,便于后续维护
- 安全性高:包含频率限制、IP 限制等安全措施
- 扩展性强:支持自定义模板和参数
- 完善的错误处理:提供详细的错误信息和日志记录
以上都是吹牛的, 其实就是一个个人发验证码的,无需企业资质解决方案
在实际使用中,建议根据业务需求进一步完善安全策略和监控机制,确保短信服务的稳定运行。
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: