本文目录导读:

我来为你提供一个完整的 PHP 封禁状态检查中间件实现:
基础中间件实现
<?php
namespace App\Middleware;
use Closure;
use App\Models\User;
use App\Services\BanService;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class CheckBanStatusMiddleware
{
protected $banService;
public function __construct(BanService $banService)
{
$this->banService = $banService;
}
public function handle(Request $request, Closure $next, string $type = 'user'): Response
{
// 获取当前认证用户
$user = $request->user();
if (!$user) {
return $next($request);
}
// 检查用户是否被封禁
if ($this->isUserBanned($user, $type)) {
return $this->handleBannedUser($request, $user);
}
// 检查IP是否被封禁
if ($this->isIpBanned($request->ip())) {
return $this->handleBannedIp($request);
}
return $next($request);
}
protected function isUserBanned($user, string $type): bool
{
// 检查用户是否有封禁记录
$ban = $user->bans()
->where('type', $type)
->where(function ($query) {
$query->whereNull('expires_at')
->orWhere('expires_at', '>', now());
})
->latest()
->first();
if (!$ban) {
return false;
}
// 检查是否永久封禁或临时封禁
if ($ban->is_permanent || $ban->expires_at > now()) {
return true;
}
return false;
}
protected function isIpBanned(string $ip): bool
{
return $this->banService->isIpBanned($ip);
}
protected function handleBannedUser($request, $user): Response
{
if ($request->expectsJson()) {
return response()->json([
'success' => false,
'message' => '您的账号已被封禁',
'data' => [
'ban_reason' => $user->currentBan?->reason,
'ban_until' => $user->currentBan?->expires_at,
'is_permanent' => $user->currentBan?->is_permanent ?? false
]
], 403);
}
// 注销用户会话
auth()->logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect()
->route('banned')
->with('error', '您的账号已被封禁');
}
protected function handleBannedIp($request): Response
{
if ($request->expectsJson()) {
return response()->json([
'success' => false,
'message' => '当前IP已被封禁'
], 403);
}
abort(403, '当前IP已被封禁');
}
}
封禁服务类
<?php
namespace App\Services;
use App\Models\User;
use App\Models\BanRecord;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Redis;
class BanService
{
/**
* 检查用户是否被封禁
*/
public function checkUserBan(User $user): ?BanRecord
{
return $user->bans()
->where(function ($query) {
$query->whereNull('expires_at')
->orWhere('expires_at', '>', now());
})
->where('is_active', true)
->latest()
->first();
}
/**
* 检查IP是否被封禁
*/
public function isIpBanned(string $ip): bool
{
// 使用缓存减少数据库查询
return Cache::remember("banned_ip:{$ip}", 3600, function () use ($ip) {
return BanRecord::where('ip_address', $ip)
->where('type', 'ip')
->where(function ($query) {
$query->whereNull('expires_at')
->orWhere('expires_at', '>', now());
})
->where('is_active', true)
->exists();
});
}
/**
* 封禁用户
*/
public function banUser(User $user, array $data): BanRecord
{
$ban = $user->bans()->create([
'type' => 'user',
'reason' => $data['reason'],
'expires_at' => $data['duration'] === 'permanent' ? null
: now()->addDays($data['duration']),
'is_permanent' => $data['duration'] === 'permanent',
'banned_by' => auth()->id(),
'ip_address' => request()->ip()
]);
// 清除相关缓存
$this->clearUserBanCache($user->id);
$this->clearIpBanCache(request()->ip());
return $ban;
}
/**
* 解除封禁
*/
public function unbanUser(int $userId): bool
{
$updated = BanRecord::where('user_id', $userId)
->where('is_active', true)
->update(['is_active' => false]);
$this->clearUserBanCache($userId);
return $updated > 0;
}
/**
* 封禁IP
*/
public function banIp(string $ip, array $data): BanRecord
{
$ban = BanRecord::create([
'type' => 'ip',
'ip_address' => $ip,
'reason' => $data['reason'] ?? 'IP封禁',
'expires_at' => $data['duration'] === 'permanent' ? null
: now()->addDays($data['duration']),
'is_permanent' => $data['duration'] === 'permanent',
'banned_by' => auth()->id()
]);
$this->clearIpBanCache($ip);
return $ban;
}
/**
* 检查登录尝试次数
*/
public function checkLoginAttempts(string $email, string $ip): array
{
$key = "login_attempts:{$email}:{$ip}";
$attempts = Redis::get($key) ?: 0;
$maxAttempts = 5;
return [
'attempts' => (int)$attempts,
'remaining' => max(0, $maxAttempts - (int)$attempts),
'is_locked' => (int)$attempts >= $maxAttempts
];
}
/**
* 记录登录失败
*/
public function recordLoginFailure(string $email, string $ip): void
{
$key = "login_attempts:{$email}:{$ip}";
$attempts = Redis::get($key) ?: 0;
Redis::setex($key, 3600, (int)$attempts + 1);
}
/**
* 清除登录失败记录
*/
public function clearLoginAttempts(string $email, string $ip): void
{
Redis::del("login_attempts:{$email}:{$ip}");
}
protected function clearUserBanCache(int $userId): void
{
Cache::forget("user_ban:{$userId}");
Cache::forget('user_ban_status:'.$userId);
}
protected function clearIpBanCache(string $ip): void
{
Cache::forget("banned_ip:{$ip}");
}
}
用户模型添加封禁关系
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class User extends Model
{
public function bans(): HasMany
{
return $this->hasMany(BanRecord::class);
}
public function currentBan(): ?BanRecord
{
return $this->bans()
->where(function ($query) {
$query->whereNull('expires_at')
->orWhere('expires_at', '>', now());
})
->where('is_active', true)
->latest()
->first();
}
public function isBanned(): bool
{
return $this->currentBan() !== null;
}
}
封禁记录模型
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class BanRecord extends Model
{
protected $fillable = [
'user_id',
'type',
'reason',
'expires_at',
'is_permanent',
'is_active',
'ip_address',
'banned_by',
'created_at',
'updated_at'
];
protected $casts = [
'expires_at' => 'datetime',
'is_permanent' => 'boolean',
'is_active' => 'boolean'
];
protected $dates = ['expires_at'];
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
public function bannedBy(): BelongsTo
{
return $this->belongsTo(User::class, 'banned_by');
}
}
路由注册中间件
<?php
// routes/web.php 或 routes/api.php
use App\Middleware\CheckBanStatusMiddleware;
// Web 路由
Route::middleware(['auth', CheckBanStatusMiddleware::class])->group(function () {
Route::get('/dashboard', 'DashboardController@index');
// 其他需要检查封禁状态的路由
});
// API 路由
Route::middleware(['auth:sanctum', CheckBanStatusMiddleware::class])->group(function () {
Route::get('/api/user/profile', 'Api\UserController@profile');
// 其他API路由
});
// 自定义封禁类型
Route::middleware(['auth', 'ban.check:shop'])->group(function () {
// 商店相关路由
});
使用示例
<?php
// 在控制器中使用
namespace App\Http\Controllers;
use App\Services\BanService;
class AdminController extends Controller
{
protected $banService;
public function __construct(BanService $banService)
{
$this->banService = $banService;
}
public function banUser(Request $request, $userId)
{
$user = User::findOrFail($userId);
$ban = $this->banService->banUser($user, [
'reason' => $request->reason,
'duration' => $request->duration // 'permanent' 或天数
]);
return response()->json([
'success' => true,
'message' => '用户已被封禁',
'data' => $ban
]);
}
public function unbanUser($userId)
{
$this->banService->unbanUser($userId);
return response()->json([
'success' => true,
'message' => '用户封禁已解除'
]);
}
}
封禁页面视图(Blade)
{{-- resources/views/banned.blade.php --}}
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header bg-danger text-white">
<h3><i class="fas fa-ban"></i> 账号封禁通知</h3>
</div>
<div class="card-body">
@if(session('error'))
<div class="alert alert-danger">
{{ session('error') }}
</div>
@endif
<p>您的账号已被封禁,如果这是误判,请联系管理员申诉。</p>
@if(isset($banReason))
<p><strong>封禁原因:</strong>{{ $banReason }}</p>
@endif
@if(isset($banUntil))
<p><strong>封禁至:</strong>{{ $banUntil->format('Y-m-d H:i:s') }}</p>
@endif
<a href="{{ route('home') }}" class="btn btn-primary">返回首页</a>
</div>
</div>
</div>
</div>
</div>
@endsection
配置封禁选项
<?php
// config/ban.php
return [
'types' => [
'user' => '用户封禁',
'ip' => 'IP封禁',
'shop' => '店铺封禁',
],
'durations' => [
'1' => '1天',
'3' => '3天',
'7' => '7天',
'30' => '30天',
'permanent' => '永久',
],
'max_login_attempts' => 5,
'login_lockout_minutes' => 60,
];
这个中间件系统包含了:
- 完整的封禁检查逻辑
- 用户和IP双重检查
- JSON和Web响应支持
- 缓存优化性能
- 灵活的封禁类型
- 封禁和解封管理
- 登录限制功能
你可以根据实际需求调整封禁逻辑和响应方式。