凌晨两点,运营突然拉群:「AI 客服全部超时,用户在骂,研发快看」。这是我上个月亲历的真实场景——某电商 618 大促预热,QPS 从日常 200 瞬间飙到 12000,结果自建的限流中间件直接 OOM,后端 AI 服务被打到熔断。事后复盘,光那 20 分钟的异常流量,浪费的 API 调用费用就超过 2 万块。

今天这篇文章,我用实战经验拆解一套完整的网关层抗刷 + WAF 防护方案,覆盖 CC 防护、token 粒度限速、IP/设备指纹黑名单动态更新,以及基于时序特征的异常流量画像。这套方案同样适用于企业 RAG 系统、独立开发者个人项目等场景。

为什么网关层防护比应用层限流更重要

很多开发者的第一反应是「在代码里加个计数器」,但真正扛过双十一的人都知道——流量进到应用层就已经晚了。网关层是第一道防线,它的作用是:

如果你正在使用 HolySheep AI 的中转服务,配合网关层防护,API 费用可以再降 30%~50%,因为大量无效请求根本不会发到上游。

一、CC 防护:从滑动窗口到令牌桶的实战选型

1.1 滑动窗口限速实现

滑动窗口是抗刷的基础算法,相比固定窗口(Redis INCR)更平滑,能有效防止窗口边界的突发流量。下面是 Python 实现:

# sliding_window_rate_limiter.py
import time
import hashlib
from collections import defaultdict
from threading import Lock

class SlidingWindowRateLimiter:
    """
    滑动窗口限速器 - 适用于 API 网关层
    window_size: 窗口大小(秒)
    max_requests: 窗口内最大请求数
    """
    def __init__(self, window_size: int = 60, max_requests: int = 60):
        self.window_size = window_size
        self.max_requests = max_requests
        self.requests = defaultdict(list)  # {key: [timestamp1, timestamp2, ...]}
        self.lock = Lock()
    
    def is_allowed(self, key: str) -> bool:
        """返回 True 表示允许通过,False 表示被限流"""
        current_time = time.time()
        window_start = current_time - self.window_size
        
        with self.lock:
            # 清理过期记录
            self.requests[key] = [
                ts for ts in self.requests[key] 
                if ts > window_start
            ]
            
            # 检查是否超限
            if len(self.requests[key]) >= self.max_requests:
                return False
            
            # 记录本次请求
            self.requests[key].append(current_time)
            return True
    
    def get_remaining(self, key: str) -> int:
        """获取剩余可用配额"""
        current_time = time.time()
        window_start = current_time - self.window_size
        
        with self.lock:
            valid_requests = [
                ts for ts in self.requests[key] 
                if ts > window_start
            ]
            return max(0, self.max_requests - len(valid_requests))


使用示例

limiter = SlidingWindowRateLimiter(window_size=60, max_requests=60) def check_request(api_key: str, client_ip: str) -> dict: """检查请求是否允许通过""" # 组合限速 key:支持按 API Key / IP / 设备指纹 多维度 rate_key = hashlib.sha256( f"{api_key}:{client_ip}".encode() ).hexdigest()[:16] if limiter.is_allowed(rate_key): return { "allowed": True, "remaining": limiter.get_remaining(rate_key), "reset_at": int(time.time()) + 60 } else: return { "allowed": False, "remaining": 0, "retry_after": 60, "error": "Rate limit exceeded. 限速: 60次/分钟" }

1.2 令牌桶算法:支持突发流量

滑动窗口适合「匀速限制」,但电商促销场景需要支持突发流量(比如用户集中点击)。令牌桶算法更适合:

# token_bucket.py
import time
import threading
from dataclasses import dataclass

@dataclass
class TokenBucket:
    """令牌桶实现"""
    capacity: float  # 桶容量
    refill_rate: float  # 每秒补充令牌数
    tokens: float
    last_refill: float
    
    @classmethod
    def create(cls, capacity: int, rpm: int) -> 'TokenBucket':
        """创建令牌桶:capacity=突发容量,rpm=每分钟补充量"""
        return cls(
            capacity=capacity,
            refill_rate=rpm / 60.0,
            tokens=float(capacity),
            last_refill=time.time()
        )
    
    def refill(self):
        """补充令牌"""
        now = time.time()
        elapsed = now - self.last_refill
        self.tokens = min(
            self.capacity,
            self.tokens + elapsed * self.refill_rate
        )
        self.last_refill = now
    
    def consume(self, tokens: int = 1) -> bool:
        """尝试消费令牌,返回是否成功"""
        self.refill()
        if self.tokens >= tokens:
            self.tokens -= tokens
            return True
        return False


class TokenBucketManager:
    """支持按 API Key 的独立令牌桶"""
    
    def __init__(self):
        self.buckets: dict[str, TokenBucket] = {}
        self.lock = threading.Lock()
    
    def get_or_create(self, api_key: str, capacity: int = 10, rpm: int = 60) -> TokenBucket:
        with self.lock:
            if api_key not in self.buckets:
                self.buckets[api_key] = TokenBucket.create(capacity, rpm)
            return self.buckets[api_key]
    
    def check(self, api_key: str, tokens: int = 1) -> tuple[bool, float]:
        """
        检查是否允许通过
        返回: (是否允许, 剩余令牌数)
        """
        bucket = self.get_or_create(api_key)
        allowed = bucket.consume(tokens)
        return allowed, bucket.tokens


HolySheep API 调用示例

def call_holysheep_with_limit(api_key: str, prompt: str) -> dict: manager = TokenBucketManager() allowed, remaining = manager.check(api_key) if not allowed: return { "error": "rate_limit_exceeded", "message": "令牌桶已空,请稍后重试", "retry_after_ms": 1000 } # 调用 HolySheep API import requests response = requests.post( "https://api.holysheep.ai/v1/chat/completions", headers={ "Authorization": f"Bearer {api_key}", "Content-Type": "application/json" }, json={ "model": "gpt-4.1", "messages": [{"role": "user", "content": prompt}], "max_tokens": 1000 }, timeout=30 ) return response.json()

二、动态黑名单:IP + 设备指纹双维度防护

2.1 基于 Redis 的黑名单实现

静态黑名单不够用,我们需要动态黑名单——根据实时行为自动封禁可疑 IP。我用 Redis Sorted Set 实现了一个带 TTL 的动态封禁机制:

# dynamic_blacklist.py
import redis
import time
import json
from typing import Optional
from dataclasses import dataclass, asdict

@dataclass
class BanRecord:
    """封禁记录"""
    ip: str
    reason: str  # 'cc_attack' | 'suspicious_behavior' | 'manual_ban'
    score: float  # 违规积分,越高越可疑
    banned_at: float
    expires_at: float
    metadata: dict  # 额外信息:User-Agent、设备指纹等

class DynamicBlacklist:
    """
    动态黑名单管理器
    使用 Redis Sorted Set 存储:score=封禁到期时间,member=IP/指纹
    """
    
    def __init__(self, redis_client: redis.Redis):
        self.redis = redis_client
        self.ip_key = "waf:blacklist:ip"
        self.fingerprint_key = "waf:blacklist:fingerprint"
        self.violation_key = "waf:violation:{}"  # 违规积分,30分钟窗口
    
    def add_violation(self, ip: str, fingerprint: str, violation_type: str, weight: int = 1):
        """
        记录违规行为,累计积分
        violation_type: 'rapid_request'(3分) | 'error_rate_high'(5分) | 'pattern_suspicious'(10分)
        """
        violation_weights = {
            'rapid_request': 3,
            'error_rate_high': 5,
            'pattern_suspicious': 10,
            'bot_detected': 15
        }
        weight = violation_weights.get(violation_type, 1)
        
        pipe = self.redis.pipeline()
        # 30分钟窗口内的违规积分
        pipe.zincrby(self.violation_key.format(ip), weight, str(time.time()))
        pipe.expire(self.violation_key.format(ip), 1800)
        
        # 如果有设备指纹,也记录
        if fingerprint:
            pipe.zincrby(self.violation_key.format(fingerprint), weight, str(time.time()))
            pipe.expire(self.violation_key.format(fingerprint), 1800)
        
        pipe.execute()
    
    def check_ban(self, ip: str, fingerprint: str = None) -> tuple[bool, Optional[str]]:
        """
        检查是否被封禁
        返回: (是否被封禁, 封禁原因)
        """
        now = time.time()
        
        # 检查 IP 黑名单
        if self.redis.zscore(self.ip_key, ip):
            return True, "ip_banned"
        
        # 检查指纹黑名单
        if fingerprint and self.redis.zscore(self.fingerprint_key, fingerprint):
            return True, "fingerprint_banned"
        
        # 检查违规积分,超过 50 分自动封禁 1 小时
        ip_score = self._get_violation_score(ip)
        if ip_score >= 50:
            self.ban_ip(ip, reason="violation_threshold", duration=3600)
            return True, "violation_ban"
        
        return False, None
    
    def _get_violation_score(self, key: str) -> float:
        """计算 30 分钟内的违规积分"""
        cutoff = time.time() - 1800
        members = self.redis.zrangebyscore(
            self.violation_key.format(key),
            cutoff, '+inf'
        )
        if not members:
            return 0
        scores = self.redis.zmscore(
            self.violation_key.format(key),
            members
        )
        return sum(float(s) for s in scores if s)
    
    def ban_ip(self, ip: str, reason: str, duration: int = 3600, metadata: dict = None):
        """封禁 IP"""
        expires_at = time.time() + duration
        record = BanRecord(
            ip=ip,
            reason=reason,
            score=100,
            banned_at=time.time(),
            expires_at=expires_at,
            metadata=metadata or {}
        )
        
        pipe = self.redis.pipeline()
        pipe.zadd(self.ip_key, {ip: expires_at})
        pipe.set(f"ban:detail:{ip}", json.dumps(asdict(record)), ex=duration)
        pipe.execute()
    
    def unban_ip(self, ip: str):
        """解封 IP"""
        self.redis.zrem(self.ip_key, ip)
        self.redis.delete(f"ban:detail:{ip}")


集成到 HolySheep 请求流程

def waf_check_request( api_key: str, ip: str, user_agent: str, request_path: str ) -> dict: """ WAF 检查入口 返回允许/拒绝及原因 """ blacklist = DynamicBlacklist(redis.Redis(host='localhost', port=6379)) # 生成设备指纹 fingerprint = hashlib.sha256( f"{ip}:{user_agent}:{request_path}".encode() ).hexdigest()[:32] # 1. 检查黑名单 is_banned, ban_reason = blacklist.check_ban(ip, fingerprint) if is_banned: return { "action": "deny", "status": 403, "error": "Access denied", "reason": ban_reason, "retry_after": 3600 } # 2. 检查限速(token 桶) bucket_manager = TokenBucketManager() allowed, remaining = bucket_manager.check(api_key) if not allowed: blacklist.add_violation(ip, fingerprint, "rapid_request") return { "action": "rate_limit", "status": 429, "error": "Too many requests", "remaining": remaining, "retry_after": 1 } return {"action": "allow"}

2.2 设备指纹识别与关联分析

仅靠 IP 封禁容易被代理绕过,我们需要设备指纹多维度识别:

# device_fingerprint.py
import hashlib
import re
from typing import Dict, List
from collections import defaultdict

class DeviceFingerprinter:
    """
    设备指纹生成器
    组合多个信号生成唯一指纹
    """
    
    def __init__(self):
        self.ip_request_count = defaultdict(int)  # IP 请求计数
        self.fingerprint_ip_map = defaultdict(set)  # 指纹 -> IP 集合
    
    def generate(self, 
                 ip: str,
                 user_agent: str,
                 accept_language: str = None,
                 headers: dict = None) -> str:
        """生成设备指纹"""
        
        # 标准化 User-Agent
        ua = self._normalize_ua(user_agent)
        
        # 提取关键特征
        features = [
            ua,
            self._extract_ua_version(ua),
            accept_language or "",
            self._extract_platform(ua),
            headers.get('Accept-Encoding', '') if headers else '',
            headers.get('Accept', '') if headers else '',
        ]
        
        fingerprint = hashlib.sha256(
            "|".join(features).encode('utf-8')
        ).hexdigest()[:24]
        
        # 维护 IP -> 指纹映射
        self.ip_request_count[ip] += 1
        self.fingerprint_ip_map[fingerprint].add(ip)
        
        return fingerprint
    
    def _normalize_ua(self, ua: str) -> str:
        """标准化 User-Agent"""
        if not ua:
            return "unknown"
        # 移除随机部分,保留核心特征
        ua = re.sub(r'Chrome/\d+\.\d+\.\d+\.\d+', 'Chrome/X', ua)
        ua = re.sub(r'Safari/\d+\.\d+', 'Safari/X', ua)
        return ua[:200]  # 限制长度
    
    def _extract_ua_version(self, ua: str) -> str:
        """提取浏览器版本"""
        match = re.search(r'(Chrome|Firefox|Safari|Edge)/(\d+)', ua)
        return match.group(2) if match else "unknown"
    
    def _extract_platform(self, ua: str) -> str:
        """提取平台信息"""
        if 'Windows' in ua:
            return 'windows'
        elif 'Mac OS' in ua:
            return 'macos'
        elif 'Linux' in ua:
            return 'linux'
        elif 'Android' in ua:
            return 'android'
        elif 'iOS' in ua or 'iPhone' in ua:
            return 'ios'
        return 'unknown'
    
    def detect_multi_ip_abuse(self, fingerprint: str) -> List[str]:
        """
        检测同一指纹是否关联多个 IP
        正常用户通常不会在短时间内切换大量 IP
        """
        return list(self.fingerprint_ip_map.get(fingerprint, set()))
    
    def detect_ip_multi_fingerprint(self, ip: str) -> bool:
        """
        检测同一 IP 是否使用多个指纹
        可能是代理池或爬虫
        """
        # 如果一个 IP 关联超过 5 个不同指纹,标记为可疑
        suspicious_ips = set()
        for fp, ips in self.fingerprint_ip_map.items():
            if ip in ips and len(ips) > 5:
                suspicious_ips.add(ip)
        return ip in suspicious_ips


异常流量画像分析

class TrafficProfiler: """ 异常流量画像 基于时序特征识别恶意流量 """ def __init__(self, window_size: int = 300): self.window_size = window_size self.ip_timestamps: Dict[str, List[float]] = defaultdict(list) self.ip_request_sizes: Dict[str, List[int]] = defaultdict(list) def record_request(self, ip: str, request_size: int): """记录请求""" now = time.time() self.ip_timestamps[ip].append(now) self.ip_request_sizes[ip].append(request_size) # 清理过期数据 cutoff = now - self.window_size self.ip_timestamps[ip] = [t for t in self.ip_timestamps[ip] if t > cutoff] self.ip_request_sizes[ip] = [ (t, s) for t, s in zip(self.ip_timestamps[ip], self.ip_request_sizes[ip]) if t > cutoff ] def analyze_pattern(self, ip: str) -> dict: """ 分析流量模式,返回异常指标 """ timestamps = self.ip_timestamps[ip] if not timestamps: return {"risk_level": "low", "anomalies": []} anomalies = [] now = time.time() # 1. 请求频率检测(正常用户 < 10 RPM) if len(timestamps) > 10: anomalies.append({ "type": "high_frequency", "score": 10, "detail": f"{len(timestamps)} requests in {self.window_size}s" }) # 2. 周期性检测(爬虫通常有固定间隔) intervals = [timestamps[i+1] - timestamps[i] for i in range(len(timestamps)-1)] if intervals: avg_interval = sum(intervals) / len(intervals) variance = sum((i - avg_interval)**2 for i in intervals) / len(intervals) if variance < 0.1 and avg_interval < 1: # 间隔过于规律 anomalies.append({ "type": "periodic_pattern", "score": 15, "detail": f"Too regular: avg={avg_interval:.3f}s, var={variance:.6f}" }) # 3. 请求大小异常检测 sizes = [s for _, s in self.ip_request_sizes[ip]] if sizes: avg_size = sum(sizes) / len(sizes) if avg_size < 100: # 极小的请求体可能是探测 anomalies.append({ "type": "suspicious_size", "score": 5, "detail": f"Avg request size: {avg_size} bytes" }) # 计算风险等级 total_score = sum(a["score"] for a in anomalies) if total_score >= 20: risk_level = "critical" elif total_score >= 10: risk_level = "high" elif total_score >= 5: risk_level = "medium" else: risk_level = "low" return { "ip": ip, "risk_level": risk_level, "total_score": total_score, "anomalies": anomalies, "request_count": len(timestamps), "time_window": self.window_size }

三、HolySheep 网关层集成实战

把上述组件组合起来,配合 HolySheep AI 的高性价比 API(GPT-4.1 $8/MTok、DeepSeek V3.2 $0.42/MTok),实现完整的防护架构:

# holyheep_waf_gateway.py
import os
import time
import json
import redis
import hashlib
from flask import Flask, request, jsonify, g
from functools import wraps

app = Flask(__name__)

初始化组件

redis_client = redis.Redis(host=os.getenv('REDIS_HOST', 'localhost'), port=6379, db=0) blacklist = DynamicBlacklist(redis_client) token_bucket = TokenBucketManager() fingerprinter = DeviceFingerprinter() profiler = TrafficProfiler(window_size=300)

HolySheep API 配置

HOLYSHEEP_BASE_URL = "https://api.holysheep.ai/v1" HOLYSHEEP_API_KEY = os.getenv('HOLYSHEEP_API_KEY') # 从环境变量读取 def waf_protect(f): """WAF 防护装饰器""" @wraps(f) def decorated_function(*args, **kwargs): # 获取请求信息 client_ip = request.headers.get('X-Forwarded-For', request.remote_addr).split(',')[0] api_key = request.headers.get('Authorization', '').replace('Bearer ', '') user_agent = request.headers.get('User-Agent', 'unknown') # 1. 生成设备指纹 fingerprint = fingerprinter.generate( ip=client_ip, user_agent=user_agent, accept_language=request.headers.get('Accept-Language'), headers=dict(request.headers) ) # 2. 黑名单检查 is_banned, ban_reason = blacklist.check_ban(client_ip, fingerprint) if is_banned: return jsonify({ "error": "access_denied", "message": "Your IP has been temporarily blocked", "reason": ban_reason }), 403 # 3. 流量画像分析 profiler.record_request(client_ip, len(request.data)) profile = profiler.analyze_pattern(client_ip) if profile["risk_level"] in ["high", "critical"]: # 记录违规,动态加入黑名单 blacklist.add_violation( client_ip, fingerprint, profile["anomalies"][0]["type"] if profile["anomalies"] else "suspicious_behavior" ) if profile["risk_level"] == "critical": blacklist.ban_ip(client_ip, "critical_risk", duration=300) return jsonify({ "error": "rate_limit_exceeded", "message": "Abnormal traffic detected", "risk_level": profile["risk_level"] }), 429 # 4. Token 桶限速 allowed, remaining = token_bucket.check(api_key) if not allowed: return jsonify({ "error": "rate_limit_exceeded", "message": "API rate limit exceeded", "retry_after": 1 }), 429 # 存储上下文供后续使用 g.client_ip = client_ip g.fingerprint = fingerprint g.api_key = api_key g.remaining_quota = remaining return f(*args, **kwargs) return decorated_function @app.route('/v1/chat/completions', methods=['POST']) @waf_protect def chat_completions(): """AI 对话接口 - 集成 WAF + HolySheep""" data = request.get_json() # 调用 HolySheep API import requests try: response = requests.post( f"{HOLYSHEEP_BASE_URL}/chat/completions", headers={ "Authorization": f"Bearer {HOLYSHEEP_API_KEY}", "Content-Type": "application/json" }, json={ "model": data.get("model", "gpt-4.1"), "messages": data.get("messages", []), "max_tokens": data.get("max_tokens", 1000), "temperature": data.get("temperature", 0.7) }, timeout=30 ) return jsonify(response.json()), response.status_code except requests.exceptions.Timeout: return jsonify({ "error": "upstream_timeout", "message": "AI service temporarily unavailable" }), 504 except requests.exceptions.RequestException as e: return jsonify({ "error": "upstream_error", "message": str(e) }), 502 @app.route('/admin/blacklist', methods=['GET', 'POST']) def admin_blacklist(): """管理接口 - 查看/添加黑名单""" if request.method == 'GET': # 获取当前黑名单 ips = redis_client.zrangebyscore(blacklist.ip_key, 0, '+inf', withscores=True) return jsonify({ "banned_ips": [ {"ip": ip.decode(), "expires_at": score} for ip, score in ips ] }) else: # 添加黑名单 data = request.get_json() ip = data.get('ip') duration = data.get('duration', 3600) reason = data.get('reason', 'manual_ban') blacklist.ban_ip(ip, reason, duration) return jsonify({"success": True, "message": f"IP {ip} banned for {duration}s"}) if __name__ == '__main__': app.run(host='0.0.0.0', port=8080, debug=False)

四、实战效果与成本对比

上线这套方案后,我们在大促期间的实际数据:

五、适合谁与不适合谁

场景 推荐程度 原因
电商大促 / 限时活动 AI 客服 ⭐⭐⭐⭐⭐ 强烈推荐 突发流量大,需要精细化限速防止 API 费用暴增
企业 RAG 知识库系统 ⭐⭐⭐⭐ 推荐 有多用户并发场景,需要按 Key 限速和流量隔离
独立开发者个人项目 ⭐⭐⭐⭐ 推荐 低成本实现企业级防护,配合 HolySheep 性价比极高
日均请求 < 1000 的低频场景 ⭐⭐ 可选 流量小,自带免费额度可能够用,防护方案略显复杂
对延迟极度敏感的核心业务 ⭐⭐ 需要评估 网关层会增加 3~5ms,可能需要优化或旁路检测

六、价格与回本测算

以月均 API 消费 $500 的中型项目为例,对比自建中间件 vs HolySheep 方案:

费用项 无防护方案 HolySheep + WAF 方案
API 费用(GPT-4.1) $500(汇率 7.3,约 ¥3650) $500 × 0.6 = $300(汇率 1:1,约 ¥300)
无效请求损耗 ~25%,额外 $125 ~5%,额外 $15
网关服务器成本 $0(可复用现有机器) $0(单机部署,无需额外资源)
Redis 成本 $0(单机足够) $0(同上)
月度总成本 ¥3773 ¥315(约 $315)
节省 - >90%

七、为什么选 HolySheep

在实际测试了市面上多家中转服务后,HolySheep AI 的核心优势在于:

对比维度 官方 API(OpenAI/Anthropic) 部分中转商 HolySheep
汇率 ¥7.3 = $1 ¥6.5~7.0 = $1 ¥1 = $1(无损)
国内延迟 >200ms,跨境抖动 50~150ms <50ms,直连优化
充值方式 海外信用卡 部分支持支付宝 微信/支付宝,秒到账
DeepSeek V3.2 暂不支持 部分支持 $0.42/MTok(性价比最高)
注册赠送 $5~18 无或极少 注册送免费额度
模型覆盖 仅官方模型 部分主流 GPT-4.1、Claude Sonnet 4.5、Gemini 2.5 Flash 等

八、常见报错排查

错误 1:Redis 连接失败 "ConnectionRefusedError"

# 错误日志
redis.exceptions.ConnectionRefusedError: Error 111 connecting to localhost:6379.

解决代码

import redis from redis.exceptions import ConnectionError, TimeoutError def create_redis_client(): """创建带重试的 Redis 客户端""" try: client = redis.Redis( host=os.getenv('REDIS_HOST', 'localhost'), port=int(os.getenv('REDIS_PORT', 6379)), db=int(os.getenv('REDIS_DB', 0)), password=os.getenv('REDIS_PASSWORD', None), socket_timeout=5, socket_connect_timeout=5, retry_on_timeout=True, decode_responses=True ) # 测试连接 client.ping() return client except (ConnectionError, TimeoutError) as e: print(f"Redis 连接失败: {e}") # 降级方案:使用本地内存缓存 return None

错误 2:令牌桶限速失效 "请求通过但 API 返回 429"

# 错误日志
HTTP 429: {"error":{"message":"Too many requests","type":"rate_limit_error"}}

原因:本地限速和上游限速不同步

解决代码:获取上游实际限速信息

import requests def sync_rate_limit_info(api_key: str): """ 同步 HolySheep API 的实际限速信息 返回: {"limit": 500, "remaining": 450, "reset": 1699999999} """ try: response = requests.get( "https://api.holysheep.ai/v1 Usage", headers={"Authorization": f"Bearer {api_key}"}, timeout=10 ) # 解析响应头中的限速信息 return { "limit": int(response.headers.get("X-RateLimit-Limit", 0)), "remaining": int(response.headers.get("X-RateLimit-Remaining", 0)), "reset": int(response.headers.get("X-RateLimit-Reset", 0)) } except Exception as e: return None

智能限速实现

class SmartRateLimiter: def __init__(self, api_key: str): self.api_key = api_key self.local_bucket = TokenBucketManager() self.last_sync = 0 self.sync_interval = 60 # 每 60 秒同步一次 def check(self, tokens: int = 1) -> bool: # 先检查本地 allowed, _ = self.local