PHPAPI限频怎么按用户

wen PHP项目 1

本文目录导读:

PHPAPI限频怎么按用户

  1. 基于Token/UserID的Redis方案(推荐)
  2. 基于数据库的方案(简单实现)
  3. 中间件实现的完整方案
  4. 按不同用户级别配置限制
  5. 使用注意:

在PHP中实现按用户的API限频,推荐以下方案:

基于Token/UserID的Redis方案(推荐)

<?php
class RateLimiter {
    private $redis;
    private $maxRequests = 100; // 限制次数
    private $timeWindow = 60;   // 时间窗口(秒)
    public function __construct() {
        $this->redis = new Redis();
        $this->redis->connect('127.0.0.1', 6379);
    }
    // 滑动窗口算法
    public function isAllowed($userId) {
        $key = "rate_limit:user:{$userId}";
        $now = time();
        // 移除窗口外的记录
        $this->redis->zRemRangeByScore($key, 0, $now - $this->timeWindow);
        // 获取当前窗口内的请求数
        $currentCount = $this->redis->zCard($key);
        if ($currentCount >= $this->maxRequests) {
            return false;
        }
        // 添加当前请求
        $this->redis->zAdd($key, $now, $now . ':' . uniqid());
        $this->redis->expire($key, $this->timeWindow);
        return true;
    }
}
// 使用示例
$limiter = new RateLimiter();
$userId = $_SERVER['HTTP_X_USER_ID'] ?? $_GET['user_id'];
if (!$limiter->isAllowed($userId)) {
    http_response_code(429);
    echo json_encode(['error' => 'Too Many Requests']);
    exit;
}
// 正常处理请求

基于数据库的方案(简单实现)

<?php
// 数据库表结构
/*
CREATE TABLE api_rate_limits (
    id INT AUTO_INCREMENT PRIMARY KEY,
    user_id INT NOT NULL,
    window_start TIMESTAMP NOT NULL,
    request_count INT DEFAULT 0,
    UNIQUE KEY idx_user_window (user_id, window_start)
);
*/
class DBRateLimiter {
    private $db;
    private $maxRequests = 100;
    private $timeWindow = 60;
    public function __construct() {
        $this->db = new PDO('mysql:host=localhost;dbname=test', 'root', '');
    }
    public function isAllowed($userId) {
        $windowStart = date('Y-m-d H:i:s', time() - $this->timeWindow);
        try {
            $this->db->beginTransaction();
            // 清理过期记录
            $stmt = $this->db->prepare("DELETE FROM api_rate_limits WHERE window_start < ?");
            $stmt->execute([$windowStart]);
            // 尝试插入或更新
            $stmt = $this->db->prepare("
                INSERT INTO api_rate_limits (user_id, window_start, request_count)
                VALUES (?, NOW(), 1)
                ON DUPLICATE KEY UPDATE request_count = request_count + 1
            ");
            $stmt->execute([$userId]);
            // 检查是否超限
            $stmt = $this->db->prepare("
                SELECT COUNT(*) as total 
                FROM api_rate_limits 
                WHERE user_id = ? AND window_start >= ?
            ");
            $stmt->execute([$userId, $windowStart]);
            $result = $stmt->fetch(PDO::FETCH_ASSOC);
            $this->db->commit();
            return $result['total'] <= $this->maxRequests;
        } catch (Exception $e) {
            $this->db->rollBack();
            return false;
        }
    }
}

中间件实现的完整方案

<?php
// Middleware接口
interface RateLimitMiddleware {
    public function handle($request, $next);
}
// Token Bucket算法实现
class TokenBucketRateLimiter implements RateLimitMiddleware {
    private $redis;
    private $config = [
        'capacity' => 100,     // 桶容量
        'refillRate' => 10,    // 每秒补充速率
        'refillTime' => 1      // 补充间隔(秒)
    ];
    public function __construct($config = []) {
        $this->redis = new Redis();
        $this->redis->connect('127.0.0.1', 6379);
        $this->config = array_merge($this->config, $config);
    }
    public function handle($request, $next) {
        $userId = $this->getUserId($request);
        $key = "token_bucket:{$userId}";
        // 获取或初始化令牌桶
        $bucket = $this->redis->hGetAll($key);
        if (empty($bucket)) {
            $bucket = [
                'tokens' => $this->config['capacity'],
                'last_refill' => time()
            ];
            $this->redis->hMSet($key, $bucket);
            $this->redis->expire($key, 3600);
        }
        // 补充令牌
        $now = time();
        $elapsed = $now - $bucket['last_refill'];
        $tokensToAdd = floor($elapsed * $this->config['refillRate']);
        if ($tokensToAdd > 0) {
            $bucket['tokens'] = min(
                $this->config['capacity'],
                $bucket['tokens'] + $tokensToAdd
            );
            $bucket['last_refill'] = $now;
            $this->redis->hMSet($key, $bucket);
        }
        // 检查是否有可用令牌
        if ($bucket['tokens'] < 1) {
            http_response_code(429);
            header('Retry-After: ' . (1 / $this->config['refillRate']));
            echo json_encode([
                'error' => 'Rate limit exceeded',
                'retry_after' => 1 / $this->config['refillRate']
            ]);
            exit;
        }
        // 消耗令牌
        $this->redis->hIncrBy($key, 'tokens', -1);
        // 设置响应头
        header('X-RateLimit-Limit: ' . $this->config['capacity']);
        header('X-RateLimit-Remaining: ' . ($bucket['tokens'] - 1));
        return $next($request);
    }
    private function getUserId($request) {
        // 根据API密钥或Token获取用户ID
        return $request['user_id'] ?? $_SERVER['HTTP_AUTHORIZATION'];
    }
}

按不同用户级别配置限制

<?php
class TieredRateLimiter {
    private $redis;
    private $tiers = [
        'free' => ['limit' => 100, 'window' => 3600],      // 每小时100次
        'basic' => ['limit' => 1000, 'window' => 3600],    // 每小时1000次
        'premium' => ['limit' => 10000, 'window' => 3600], // 每小时10000次
        'enterprise' => ['limit' => 100000, 'window' => 3600]
    ];
    public function __construct() {
        $this->redis = new Redis();
        $this->redis->connect('127.0.0.1', 6379);
    }
    public function checkRateLimit($userId, $userTier = 'free') {
        $tier = $this->tiers[$userTier] ?? $this->tiers['free'];
        $key = "rate_limit:{$userTier}:{$userId}";
        $currentCount = $this->redis->incr($key);
        if ($currentCount === 1) {
            $this->redis->expire($key, $tier['window']);
        }
        if ($currentCount > $tier['limit']) {
            $ttl = $this->redis->ttl($key);
            return [
                'allowed' => false,
                'retry_after' => $ttl,
                'limit' => $tier['limit'],
                'remaining' => 0
            ];
        }
        return [
            'allowed' => true,
            'limit' => $tier['limit'],
            'remaining' => $tier['limit'] - $currentCount
        ];
    }
}

使用注意:

  1. 用户识别:确保能正确识别用户(JWT Token、API Key、Session等)
  2. 性能优化
    • 使用Redis的Lua脚本实现原子操作
    • 考虑使用Local Cache减轻Redis压力
  3. 分布式环境
    • Redis集群支持
    • 考虑使用一致性哈希
  4. 错误处理
    • 返回适当的HTTP状态码(429)
    • 包含Retry-After头
    • 提供重试信息

根据你的具体需求选择合适的方案,Redis方案性能和灵活性最佳。

抱歉,评论功能暂时关闭!