You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 20:11:48