Laravel 文件下载频率限制完整实现方案

Laravel 文件下载频率限制完整实现方案

在 Laravel 中限制文件下载频率可以有效防止资源滥用和 DDoS 攻击。以下是几种不同级别的实现方法:

1. 基础速率限制(路由中间件)

使用内置 throttle 中间件

// routes/web.php
Route::middleware('throttle:10,1')->group(function () {
    // 限制每小时10次下载
    Route::get('/download/{file}', [FileController::class, 'download']);
});

// 或者针对特定路由
Route::get('/download/{file}', [FileController::class, 'download'])
    ->middleware('throttle:5,1'); // 每分钟5次

自定义速率限制配置

// app/Http/Kernel.php
protected $middlewareGroups = [
    'web' => [
        \Illuminate\Routing\Middleware\ThrottleRequests::class.':download_limit',
    ],
];

// 在 RouteServiceProvider 中定义限制
protected function configureRateLimiting()
{
    RateLimiter::for('download_limit', function (Request $request) {
        return Limit::perMinute(5)->by($request->ip()); // 按IP限制
        // 或者按用户ID限制(需登录)
        // return Limit::perHour(10)->by($request->user()?->id ?: $request->ip());
    });
}

2. 高级用户分级限制

基于用户角色的差异化限制

RateLimiter::for('downloads', function (Request $request) {
    $user = $request->user();
    
    if ($user?->hasRole('vip')) {
        return Limit::perMinute(30); // VIP用户限制
    } 
    
    if ($user?->hasRole('premium')) {
        return Limit::perMinute(15); // 高级用户
    }
    
    return Limit::perMinute(5) // 普通用户
        ->response(function (Request $request, array $headers) {
            return response('下载频率过高,请升级账户获取更高下载配额', 429, $headers);
        });
});

3. 文件类型差异化限制

不同文件类型不同限制

public function download($fileId)
{
    $file = File::findOrFail($fileId);
    
    // 根据文件类型设置不同限制
    $limitKey = match($file->type) {
        'video' => 'video_download',
        'large' => 'large_file',
        default => 'regular_download'
    };
    
    if (RateLimiter::tooManyAttempts($limitKey, 5)) {
        $seconds = RateLimiter::availableIn($limitKey);
        abort(429, "请等待 {$seconds} 秒后再试");
    }
    
    RateLimiter::hit($limitKey);
    
    return Storage::download($file->path);
}

4. 动态限制调整

数据库驱动的动态限制

RateLimiter::for('dynamic_downloads', function (Request $request) {
    $user = $request->user();
    $baseLimit = config('downloads.base_limit', 5);
    
    if ($user) {
        // 从数据库获取用户自定义限制
        $userLimit = $user->download_limit ?? $baseLimit;
        return Limit::perHour($userLimit);
    }
    
    return Limit::perHour($baseLimit)->by($request->ip());
});

5. 分布式系统限制(Redis)

使用Redis实现集群级限制

// config/cache.php 确保使用redis驱动
'redis' => [
    'driver' => 'redis',
    'connection' => 'default',
],

// 速率限制器配置
RateLimiter::for('global_downloads', function (Request $request) {
    return Limit::perMinute(100)
        ->by($request->ip())
        ->response(function (Request $request, array $headers) {
            $retryAfter = $headers['X-RateLimit-Reset'] - time();
            return response()->json([
                'error' => 'Too many requests',
                'retry_after' => $retryAfter
            ], 429);
        });
});

6. 前端友好提示

返回详细限制信息

// 在AppServiceProvider中
Response::macro('tooManyAttempts', function ($retryAfter) {
    return response()->json([
        'message' => '请求过于频繁',
        'retry_after' => $retryAfter,
        'wait_seconds' => $retryAfter - time()
    ], 429);
});

// 在控制器中
if (RateLimiter::tooManyAttempts('download:'.$user->id, 5)) {
    return Response::tooManyAttempts(
        RateLimiter::availableIn('download:'.$user->id)
    );
}

7. 完整控制器示例

use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Facades\Storage;

public function download($fileId)
{
    $user = auth()->user();
    $file = File::findOrFail($fileId);
    
    // 权限检查
    if (!$user->can('download', $file)) {
        abort(403);
    }
    
    // 创建唯一的限制键(用户ID+文件类型)
    $limitKey = 'download:'.$user->id.':'.$file->type;
    $maxAttempts = $file->type === 'premium' ? 3 : 10;
    
    // 检查限制
    if (RateLimiter::tooManyAttempts($limitKey, $maxAttempts)) {
        $seconds = RateLimiter::availableIn($limitKey);
        return response()->view('errors.429', [
            'retryAfter' => $seconds,
            'limit' => $maxAttempts,
            'period' => '小时'
        ], 429);
    }
    
    // 增加计数
    RateLimiter::hit($limitKey, 3600); // 1小时窗口
    
    // 记录下载日志
    DownloadLog::create([
        'user_id' => $user->id,
        'file_id' => $file->id,
        'ip_address' => request()->ip()
    ]);
    
    // 执行下载
    return Storage::disk('private')->download($file->path);
}

8. 测试速率限制

// 测试用例示例
public function test_download_rate_limiting()
{
    $user = User::factory()->create();
    $file = File::factory()->create();
    
    // 测试正常下载
    for ($i = 0; $i < 5; $i++) {
        $this->actingAs($user)
            ->get("/download/{$file->id}")
            ->assertOk();
    }
    
    // 测试超过限制
    $response = $this->actingAs($user)
        ->get("/download/{$file->id}");
    
    $response->assertStatus(429);
    $this->assertStringContainsString(
        '请等待',
        $response->getContent()
    );
}

最佳实践建议

  1. 分层限制:同时实施IP级别和用户级别的限制
  2. 合理设置窗口:根据业务需求选择分钟/小时/天为单位
  3. 敏感文件更严格:对重要文件设置更低的限制阈值
  4. 监控和调整:定期检查限制日志,调整不合理的限制值
  5. 用户体验:清晰提示用户剩余下载次数和恢复时间
  6. 白名单机制:对可信IP或内部系统免除限制

通过以上方法,可以构建一个灵活高效的文件下载频率控制系统,既能防止滥用,又能保证正常用户的下载体验。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值