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()
);
}
最佳实践建议
- 分层限制:同时实施IP级别和用户级别的限制
- 合理设置窗口:根据业务需求选择分钟/小时/天为单位
- 敏感文件更严格:对重要文件设置更低的限制阈值
- 监控和调整:定期检查限制日志,调整不合理的限制值
- 用户体验:清晰提示用户剩余下载次数和恢复时间
- 白名单机制:对可信IP或内部系统免除限制
通过以上方法,可以构建一个灵活高效的文件下载频率控制系统,既能防止滥用,又能保证正常用户的下载体验。