Laravel中手机号更新场景下未收到OTP的重发功能实现咨询
实现Vonage OTP重发功能的方案
先梳理下现有代码的核心问题:直接更新用户手机号却未经过OTP验证,逻辑不完整;验证码未关联用户和目标手机号,无法追踪对应关系;缺少重发限制逻辑,易被滥用;使用echo输出不符合Laravel响应规范。下面是完整的重发功能实现步骤:
1. 调整VerificationCode模型与数据库表
首先给verification_codes表添加关联字段,用于绑定用户、待验证手机号和发送记录:
生成迁移文件:
php artisan make:migration add_fields_to_verification_codes_table
迁移文件内容:
public function up() { Schema::table('verification_codes', function (Blueprint $table) { $table->foreignId('user_id')->constrained()->onDelete('cascade'); $table->string('phone_number'); $table->timestamp('last_sent_at')->nullable(); $table->timestamp('used_at')->nullable(); // 标记验证码是否已使用 }); } public function down() { Schema::table('verification_codes', function (Blueprint $table) { $table->dropForeign(['user_id']); $table->dropColumn(['user_id', 'phone_number', 'last_sent_at', 'used_at']); }); }
运行迁移:
php artisan migrate
更新VerificationCode模型的可填充字段:
class VerificationCode extends Model { use HasFactory; protected $fillable = ['otp', 'expire_at', 'user_id', 'phone_number', 'last_sent_at', 'used_at']; }
2. 重构手机号更新的初始发送流程
修改原updatePhoneNumber方法,不再直接更新用户手机号,而是生成/更新验证码并发送OTP:
use Illuminate\Support\Facades\Auth; use Carbon\Carbon; use Vonage\Client; use Vonage\Client\Credentials\Basic; use Vonage\SMS\Message\SMS; public function updatePhoneNumber(Request $request) { $request->validate([ 'telefono' => 'required|unique:users,telefono' ]); $phoneNumber = $request->input('telefono'); $user = Auth::user(); // 查找用户对应手机号的未过期验证码,无则创建 $verificationCode = VerificationCode::firstOrCreate( ['user_id' => $user->id, 'phone_number' => $phoneNumber], [ 'otp' => rand(10000, 99999), 'expire_at' => Carbon::now()->addMinutes(10), 'last_sent_at' => Carbon::now() ] ); // 限制60秒内重复发送 if ($verificationCode->exists && $verificationCode->last_sent_at->diffInSeconds(Carbon::now()) < 60) { return response()->json(['message' => '请等待60秒后再重试'], 429); } // 更新验证码内容与发送时间(针对已存在的记录) if ($verificationCode->exists) { $verificationCode->update([ 'otp' => rand(10000, 99999), 'expire_at' => Carbon::now()->addMinutes(10), 'last_sent_at' => Carbon::now(), 'used_at' => null // 重置使用状态 ]); } // 初始化Vonage客户端 $basic = new Basic("44bc4bb2", "fYVcLeo0lMhmtjm1"); $client = new Client($basic); try { $response = $client->sms()->send( new SMS($phoneNumber, 'Help4You', 'Il tuo codice di verifica è:' . "\n" . $verificationCode->otp) ); $message = $response->current(); if ($message->getStatus() != 0) { return response()->json(['message' => '验证码发送失败,请稍后重试'], 500); } return response()->json(['message' => '验证码已发送,请查收']); } catch (\Exception $e) { return response()->json(['message' => '验证码发送失败:' . $e->getMessage()], 500); } }
3. 实现OTP重发接口
新增专门的重发方法,验证用户是否存在未过期的待验证请求:
public function resendOtp(Request $request) { $user = Auth::user(); // 获取用户最近的未过期、未使用的验证码 $verificationCode = VerificationCode::where('user_id', $user->id) ->where('expire_at', '>', Carbon::now()) ->whereNull('used_at') ->latest() ->first(); if (!$verificationCode) { return response()->json(['message' => '没有待验证的请求,请先提交手机号'], 404); } // 60秒内禁止重发 if ($verificationCode->last_sent_at->diffInSeconds(Carbon::now()) < 60) { return response()->json(['message' => '请等待60秒后再重试'], 429); } // 更新验证码与发送时间 $verificationCode->update([ 'otp' => rand(10000, 99999), 'expire_at' => Carbon::now()->addMinutes(10), 'last_sent_at' => Carbon::now() ]); // 重新发送OTP $basic = new Basic("44bc4bb2", "fYVcLeo0lMhmtjm1"); $client = new Client($basic); try { $response = $client->sms()->send( new SMS($verificationCode->phone_number, 'Help4You', 'Il tuo codice di verifica è:' . "\n" . $verificationCode->otp) ); $message = $response->current(); if ($message->getStatus() != 0) { return response()->json(['message' => '验证码重发失败,请稍后重试'], 500); } return response()->json(['message' => '验证码已重发,请查收']); } catch (\Exception $e) { return response()->json(['message' => '验证码重发失败:' . $e->getMessage()], 500); } }
4. 新增OTP验证接口(完成手机号更新)
用户输入OTP验证通过后,再正式更新手机号:
public function verifyOtp(Request $request) { $request->validate([ 'otp' => 'required|digits:5' ]); $user = Auth::user(); $otp = $request->input('otp'); // 查找匹配的未过期、未使用的验证码 $verificationCode = VerificationCode::where('user_id', $user->id) ->where('otp', $otp) ->where('expire_at', '>', Carbon::now()) ->whereNull('used_at') ->first(); if (!$verificationCode) { return response()->json(['message' => '验证码无效或已过期'], 400); } // 更新用户手机号 $user->update(['telefono' => $verificationCode->phone_number]); // 标记验证码已使用 $verificationCode->update(['used_at' => Carbon::now()]); return response()->json(['message' => '手机号更新成功']); }
5. 配置路由
在routes/api.php(或web路由)中添加接口路由:
use App\Http\Controllers\YourController; Route::middleware('auth')->group(function () { Route::post('update-phone', [YourController::class, 'updatePhoneNumber']); Route::post('resend-otp', [YourController::class, 'resendOtp']); Route::post('verify-otp', [YourController::class, 'verifyOtp']); });
额外优化建议
- 不要硬编码Vonage密钥:将密钥写入
.env文件,通过config/services.php配置,然后在服务容器中绑定客户端实现依赖注入。 - 增加重发次数限制:可添加
attempts字段,限制每个手机号最多重发3次,超过则需重新提交手机号。
内容的提问来源于stack exchange,提问作者Leonardo Gori
相关产品推荐
相关产品推荐

