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

Laravel Sanctum结合SQL Server生成Token时日期转换异常问题

Laravel Sanctum结合SQL Server生成Token时的日期转换错误

使用Laravel Sanctum实现登录认证,调用createToken生成Token时触发如下SQL错误:

SQLSTATE[22007]: [Microsoft][ODBC Driver 17 for SQL Server][SQL Server]Converting an nvarchar data type to a datetime data type resulted in an out-of-range value. (Connection: sqlsrv, SQL: insert into [personal_access_tokens] ([name], [token], [abilities], [expires_at], [tokenable_id], [tokenable_type], [updated_at], [created_at]) values (insomnia, e1984b68e31dfba8268194d6f7190c65e2702904c4759084634086f970cc9830, ["*"], ?, 1, App\Models\User, 2024-09-24 12:12:03.047, 2024-09-24 12:12:03.047))

但直接执行原生SQL或通过DB::raw插入数据时却能正常运行:

insert into [personal_access_tokens] ([name], [token], [abilities], [expires_at], [tokenable_id], [tokenable_type], [updated_at], [created_at])
values ('insominia', 'd6f25421129a1ad0d05a82d83ded92859d4209272981f46c3b5377503dbfd312', '["*"]', CONVERT(datetime, '2050-02-23 06:20:24.000', 121), 1, 'App\Models\User', CONVERT(datetime, '2016-02-23 06:20:24.000', 121), CONVERT(datetime, '2016-02-23 06:20:24.000', 121))

DB::raw("insert into [personal_access_tokens] ([name], [token], [abilities], [expires_at], [tokenable_id], [tokenable_type], [updated_at], [created_at])
values ('insominia', 'd6f25421129a1ad0d05a82d83ded92859d4209272981f46c3b5377503dbfd312', '["*"]', CONVERT(datetime, '2050-02-23 06:20:24.000', 121), 1, 'App\Models\User', CONVERT(datetime, '2016-02-23 06:20:24.000', 121), CONVERT(datetime, '2016-02-23 06:20:24.000', 121))")

当前使用的登录函数代码:

public function login(Request $request)
{
    $user = User::where('email', $request->email)->first();
    $token = $user->createToken($request->device_name)->plainTextToken;
    return response()->json([
        'token' => $token,
        'user' => $user
    ]);
}

问题原因

错误日志中expires_at的占位符为?,说明Laravel在绑定日期参数时,未生成SQL Server兼容的datetime格式或范围值。SQL Server的datetime类型支持的日期范围是1753-01-01 到 9999-12-31,若Sanctum默认生成的过期时间超出该范围,或日期格式不匹配,就会触发转换错误。

解决方案

1. 显式指定Token过期时间(兼容datetime范围)

调用createToken时,手动设置一个在SQL Server datetime范围内的过期时间,例如20年后:

use Carbon\Carbon;

public function login(Request $request)
{
    $user = User::where('email', $request->email)->first();
    // 显式设置过期时间,确保在SQL Server datetime范围内
    $token = $user->createToken(
        $request->device_name,
        ['*'],
        Carbon::now()->addYears(20)
    )->plainTextToken;
    return response()->json([
        'token' => $token,
        'user' => $user
    ]);
}

2. 修改表字段类型为datetime2

SQL Server的datetime2类型支持更大的日期范围(0001-01-01 到 9999-12-31),与Laravel的Carbon日期兼容性更好。修改personal_access_tokens表的迁移文件:

// 原代码
$table->datetime('expires_at')->nullable();

// 修改为
$table->datetime2('expires_at')->nullable();

执行迁移刷新命令更新表结构:

php artisan migrate:refresh

3. 重写Sanctum的createToken方法处理日期格式

在User模型中重写HasApiTokens trait的createToken方法,通过DB::raw手动转换日期格式:

use Laravel\Sanctum\HasApiTokens;
use Laravel\Sanctum\NewAccessToken;
use Illuminate\Support\Str;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;

class User extends Model
{
    use HasApiTokens;

    public function createToken(string $name, array $abilities = ['*'], ?Carbon $expiresAt = null)
    {
        $plainTextToken = Str::random(40);

        $token = $this->tokens()->create([
            'name' => $name,
            'token' => hash('sha256', $plainTextToken),
            'abilities' => $abilities,
            'expires_at' => $expiresAt ? DB::raw("CONVERT(datetime2, '{$expiresAt->format('Y-m-d H:i:s.u')}', 121)") : null,
        ]);

        return new NewAccessToken($token, $plainTextToken);
    }
}

内容的提问来源于stack exchange,提问作者Edinho Rodrigues

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 00:05:56