File size: 5,270 Bytes
c60e0ef
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# rate_limiter.py
# ==========================================
# 🔒 P0安全优化:API 限流配置
# ==========================================
# 作用:防止恶意刷接口、暴力破解、DDoS 攻击
# 关联文件:
#   - app.py (全局限流器初始化)
#   - 各 router_*.py (应用限流装饰器)
# ==========================================

from slowapi import Limiter
from slowapi.util import get_remote_address
from functools import wraps
import time
import logging

logger = logging.getLogger("ComfyUI-Ranking.RateLimiter")

# ==========================================
# 📊 限流配置
# ==========================================

# 全局限流器实例(在 app.py 中初始化)
limiter = Limiter(key_func=get_remote_address)

# 限流规则定义
RATE_LIMITS = {
    # 🔐 认证相关(严格限制)
    "login": "5/minute",           # 登录:每分钟5次
    "register": "3/minute",         # 注册:每分钟3次
    "reset_password": "3/minute",   # 重置密码:每分钟3次
    "send_code": "1/minute",        # 发送验证码:每分钟1次
    
    # 💰 金融操作(严格限制)
    "purchase": "10/minute",        # 购买:每分钟10次
    "refund": "3/minute",           # 退款:每分钟3次
    "withdraw": "3/minute",         # 提现:每分钟3次
    "tip": "20/minute",             # 打赏:每分钟20次
    
    # 📝 内容操作(中等限制)
    "create_item": "10/minute",     # 发布内容:每分钟10次
    "update_item": "30/minute",     # 更新内容:每分钟30次
    "create_task": "5/minute",      # 创建任务:每分钟5次
    "create_post": "10/minute",     # 发帖:每分钟10次
    "comment": "30/minute",         # 评论:每分钟30次
    
    # 🔄 互动操作(宽松限制)
    "like": "60/minute",            # 点赞:每分钟60次
    "follow": "30/minute",          # 关注:每分钟30次
    "message": "30/minute",         # 私信:每分钟30次
    
    # 📖 查询操作(最宽松)
    "read": "100/minute",           # 读取:每分钟100次
    "search": "30/minute",          # 搜索:每分钟30次
    
    # 📤 上传操作(严格限制)
    "upload": "20/minute",          # 上传:每分钟20次
    
    # 🌐 默认限制
    "default": "60/minute",         # 默认:每分钟60次
}


# ==========================================
# 🔧 限流工具函数
# ==========================================

def get_limit(action: str) -> str:
    """获取指定操作的限流规则"""
    return RATE_LIMITS.get(action, RATE_LIMITS["default"])


# ==========================================
# 🛡️ 用户级限流(基于账号)
# ==========================================

# 用户请求计数器
user_request_counts = {}
USER_LIMIT_WINDOW = 60  # 时间窗口(秒)
USER_LIMIT_MAX = 100    # 每用户每分钟最大请求数


def check_user_rate_limit(account: str) -> bool:
    """

    检查用户级限流

    返回 True 表示允许请求,False 表示超限

    """
    if not account:
        return True
    
    now = time.time()
    key = f"user:{account}"
    
    # 清理过期记录
    if key in user_request_counts:
        user_request_counts[key] = [
            t for t in user_request_counts[key] 
            if now - t < USER_LIMIT_WINDOW
        ]
    else:
        user_request_counts[key] = []
    
    # 检查是否超限
    if len(user_request_counts[key]) >= USER_LIMIT_MAX:
        logger.warning(f"RATE_LIMIT | user={account} | requests={len(user_request_counts[key])}")
        return False
    
    # 记录请求
    user_request_counts[key].append(now)
    return True


def user_rate_limit_decorator(func):
    """用户级限流装饰器"""
    @wraps(func)
    async def wrapper(*args, **kwargs):
        # 尝试从参数中获取用户账号
        account = kwargs.get('current_user') or kwargs.get('account')
        
        if account and not check_user_rate_limit(account):
            from fastapi import HTTPException
            raise HTTPException(
                status_code=429, 
                detail="请求过于频繁,请稍后再试"
            )
        
        return await func(*args, **kwargs)
    return wrapper


# ==========================================
# 📊 限流统计
# ==========================================

# IP 请求统计(用于监控)
ip_stats = {}


def record_request(ip: str, endpoint: str):
    """记录请求用于统计"""
    now = time.time()
    key = f"{ip}:{endpoint}"
    
    if key not in ip_stats:
        ip_stats[key] = {"count": 0, "first_request": now}
    
    ip_stats[key]["count"] += 1
    ip_stats[key]["last_request"] = now


def get_ip_stats():
    """获取 IP 请求统计"""
    return ip_stats


def clear_old_stats(max_age: int = 3600):
    """清理超过指定时间的统计数据"""
    now = time.time()
    keys_to_remove = [
        k for k, v in ip_stats.items() 
        if now - v.get("last_request", 0) > max_age
    ]
    for k in keys_to_remove:
        del ip_stats[k]